diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81246706c..2c07660df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,10 +10,6 @@ concurrency: group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} cancel-in-progress: true -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - jobs: build-nix: runs-on: ubuntu-latest @@ -33,16 +29,20 @@ jobs: - '**/*.go' - 'integration_test/' - 'config-example.yaml' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Check vendor hash id: vendorhash if: steps.changed-files.outputs.files == 'true' run: | - go run ./cmd/vendorhash check | tee check-result + nix develop --command -- go run ./cmd/vendorhash check | tee check-result { grep '^expected_sri=' check-result || true grep '^actual_sri=' check-result || true @@ -63,7 +63,7 @@ jobs: - name: Run nix build if: steps.changed-files.outputs.files == 'true' - run: nix build --fallback + run: nix build - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: steps.changed-files.outputs.files == 'true' @@ -81,13 +81,17 @@ jobs: - "GOARCH=amd64 GOOS=darwin" steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Run go cross compile env: CGO_ENABLED: 0 - run: env ${{ matrix.env }} go build -o "headscale" + run: env ${{ matrix.env }} nix develop --command -- go build -o "headscale" ./cmd/headscale - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index fffc3da04..43f1d62d4 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -12,10 +12,6 @@ concurrency: group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} cancel-in-progress: true -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - jobs: check-generated: runs-on: ubuntu-latest @@ -32,15 +28,20 @@ jobs: - '*.nix' - 'go.*' - '**/*.go' + - '**/*.proto' + - 'buf.gen.yaml' - 'tools/**' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Run make generate if: steps.changed-files.outputs.files == 'true' - run: make generate + run: nix develop --command -- make generate - name: Check for uncommitted changes if: steps.changed-files.outputs.files == 'true' diff --git a/.github/workflows/check-tests.yaml b/.github/workflows/check-tests.yaml index b73011732..63a181413 100644 --- a/.github/workflows/check-tests.yaml +++ b/.github/workflows/check-tests.yaml @@ -6,10 +6,6 @@ concurrency: group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} cancel-in-progress: true -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - jobs: check-tests: runs-on: ubuntu-latest @@ -28,15 +24,19 @@ jobs: - '**/*.go' - 'integration_test/' - 'config-example.yaml' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Generate and check integration tests if: steps.changed-files.outputs.files == 'true' run: | - (cd .github/workflows && go generate) + nix develop --command bash -c "cd .github/workflows && go generate" git diff --exit-code .github/workflows/test-integration.yaml - name: Show missing tests diff --git a/.github/workflows/container-main.yml b/.github/workflows/container-main.yml index e2ebe6fc0..9b55a273e 100644 --- a/.github/workflows/container-main.yml +++ b/.github/workflows/container-main.yml @@ -16,10 +16,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.sha }} cancel-in-progress: true -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - jobs: container: if: github.repository == 'juanfont/headscale' @@ -44,8 +40,12 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Set commit timestamp run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV @@ -56,7 +56,7 @@ jobs: KO_DEFAULTBASEIMAGE: gcr.io/distroless/base-debian13 CGO_ENABLED: "0" run: | - ko build \ + nix develop --command -- ko build \ --bare \ --platform=linux/amd64,linux/arm64 \ --tags=main-${GITHUB_SHA::7},development \ @@ -68,7 +68,7 @@ jobs: KO_DEFAULTBASEIMAGE: gcr.io/distroless/base-debian13 CGO_ENABLED: "0" run: | - ko build \ + nix develop --command -- ko build \ --bare \ --platform=linux/amd64,linux/arm64 \ --tags=main-${GITHUB_SHA::7},development \ @@ -92,15 +92,19 @@ jobs: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Build binary env: CGO_ENABLED: "0" GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} - run: go build -o headscale ./cmd/headscale + run: nix develop --command -- go build -o headscale ./cmd/headscale - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: diff --git a/.github/workflows/gc.yml b/.github/workflows/gc.yml deleted file mode 100644 index 9df7a0370..000000000 --- a/.github/workflows/gc.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Cache GC - -# Garbage collection for the hestia binary cache. Must run on the default -# branch: a PR job's cache scope is read-only towards the default branch and -# dies with the PR, but the default-branch scope grows forever without GC. - -concurrency: - group: hestia-gc - cancel-in-progress: false - -on: - schedule: - # Daily, off-peak (UTC). - - cron: "23 3 * * *" - workflow_dispatch: - inputs: - dry-run: - description: Plan only; do not repack, touch, or delete anything. - type: boolean - default: false - -permissions: - contents: read - -jobs: - gc: - runs-on: ubuntu-latest - permissions: - # REST cache deletes need actions:write. - actions: write - contents: read - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: Run garbage collection - run: '"${HESTIA_BIN}" gc ${{ inputs.dry-run && ''--dry-run'' || '''' }}' - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/gh-action-integration-generator.go b/.github/workflows/gh-action-integration-generator.go index 5bb8318bd..3c1e25ca1 100644 --- a/.github/workflows/gh-action-integration-generator.go +++ b/.github/workflows/gh-action-integration-generator.go @@ -14,7 +14,7 @@ import ( // Key is the test function name, value is a list of subtest prefixes. // Each prefix becomes a separate CI job as "TestName/prefix". // -// Example: [TestAutoApproveMultiNetwork] has subtests like: +// Example: TestAutoApproveMultiNetwork has subtests like: // - TestAutoApproveMultiNetwork/authkey-tag-advertiseduringup-false-pol-database // - TestAutoApproveMultiNetwork/webauth-user-advertiseduringup-true-pol-file // diff --git a/.github/workflows/integration-test-template.yml b/.github/workflows/integration-test-template.yml index 590185c96..75a1d275b 100644 --- a/.github/workflows/integration-test-template.yml +++ b/.github/workflows/integration-test-template.yml @@ -51,11 +51,6 @@ jobs: with: name: tailscale-head-image path: /tmp/artifacts - - name: Download tailscale released images - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: tailscale-released-images - path: /tmp/artifacts - name: Download hi binary uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: @@ -72,33 +67,28 @@ jobs: with: name: postgres-image path: /tmp/artifacts - - name: Force overlay2 storage driver + - name: Pin Docker to v28 (avoid v29 breaking changes) run: | - sudo mkdir -p /etc/docker - echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json + # Docker 29 breaks docker build via Go client libraries and + # docker load/save with certain tarball formats. + # Pin to Docker 28.x until our tooling is updated. + # https://github.com/actions/runner-images/issues/13474 + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update -qq + VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}') + sudo apt-get install -y --allow-downgrades \ + "docker-ce=${VERSION}" "docker-ce-cli=${VERSION}" sudo systemctl restart docker docker version - - name: Load br_netfilter for in-cluster service routing - if: inputs.test == 'TestK8sOperator' - # TestK8sOperator runs k3s in a container; without br_netfilter on the - # host, bridged pod-to-pod traffic skips kube-proxy's ClusterIP DNAT and - # in-cluster DNS (kube-dns) is unreachable. The module cannot be loaded - # from inside the unprivileged-module rancher/k3s image, so load it here. - run: sudo modprobe br_netfilter - - name: Login to Docker Hub - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} - if: env.DOCKERHUB_USERNAME != '' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ env.DOCKERHUB_TOKEN }} - name: Load Docker images, Go cache, and prepare binary run: | gunzip -c /tmp/artifacts/headscale-image.tar.gz | docker load gunzip -c /tmp/artifacts/tailscale-head-image.tar.gz | docker load - gunzip -c /tmp/artifacts/tailscale-released-images.tar.gz | docker load if [ -f /tmp/artifacts/postgres-image.tar.gz ]; then gunzip -c /tmp/artifacts/postgres-image.tar.gz | docker load fi @@ -115,12 +105,6 @@ jobs: HEADSCALE_INTEGRATION_POSTGRES_IMAGE: ${{ inputs.postgres_flag == '--postgres=1' && format('postgres:{0}', github.sha) || '' }} HEADSCALE_INTEGRATION_GO_CACHE: /tmp/go-cache/go HEADSCALE_INTEGRATION_GO_BUILD_CACHE: /tmp/go-cache/.cache/go-build - # Mirror the docker/login-action secrets into env so the - # dockertestutil.Credentials resolver picks them up directly - # (otherwise it falls back to parsing ~/.docker/config.json, - # which works but is one step further from the source). - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} run: /tmp/artifacts/hi run --stats --ts-memory-limit=300 --hs-memory-limit=1500 "^${{ inputs.test }}$" \ --timeout=120m \ ${{ inputs.postgres_flag }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..5e8ab5c4a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,93 @@ +name: Lint + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + golangci-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + - name: Get changed files + id: changed-files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + files: + - '*.nix' + - 'go.*' + - '**/*.go' + - 'integration_test/' + - 'config-example.yaml' + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + if: steps.changed-files.outputs.files == 'true' + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} + + - name: golangci-lint + if: steps.changed-files.outputs.files == 'true' + run: nix develop --command -- golangci-lint run + --new-from-rev=${{github.event.pull_request.base.sha}} + --output.text.path=stdout + --output.text.print-linter-name + --output.text.print-issued-lines + --output.text.colors + + prettier-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 2 + - name: Get changed files + id: changed-files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + files: + - '*.nix' + - '**/*.md' + - '**/*.yml' + - '**/*.yaml' + - '**/*.ts' + - '**/*.js' + - '**/*.sass' + - '**/*.css' + - '**/*.scss' + - '**/*.html' + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + if: steps.changed-files.outputs.files == 'true' + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} + + - name: Prettify code + if: steps.changed-files.outputs.files == 'true' + run: nix develop --command -- prettier --no-error-on-unmatched-pattern + --ignore-unknown --check **/*.{ts,js,md,yaml,yml,sass,css,scss,html} + + proto-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} + + - name: Buf lint + run: nix develop --command -- buf lint proto diff --git a/.github/workflows/nix-checks.yml b/.github/workflows/nix-checks.yml deleted file mode 100644 index 03c4b8e8b..000000000 --- a/.github/workflows/nix-checks.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Nix Flake Checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -# Each job only runs `nix build .#checks..`; the check logic lives -# in flake.nix via the flake-checks library. The fileset-filtered checks hit the -# hestia cache when their inputs are unchanged, so no changed-files gating. -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: build - run: nix build -L .#checks.x86_64-linux.build - - gotest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: gotest - run: nix build -L .#checks.x86_64-linux.gotest - - golangci-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: golangci-lint - run: nix build -L .#checks.x86_64-linux.golangci-lint - - formatting: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: formatting - run: nix build -L .#checks.x86_64-linux.formatting diff --git a/.github/workflows/nix-module-test.yml b/.github/workflows/nix-module-test.yml index 1a7fbcc85..68ad95456 100644 --- a/.github/workflows/nix-module-test.yml +++ b/.github/workflows/nix-module-test.yml @@ -38,14 +38,18 @@ jobs: - 'cmd/**' - 'hscontrol/**' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 if: steps.changed-files.outputs.nix == 'true' || steps.changed-files.outputs.go == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 if: steps.changed-files.outputs.nix == 'true' || steps.changed-files.outputs.go == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Run NixOS module tests if: steps.changed-files.outputs.nix == 'true' || steps.changed-files.outputs.go == 'true' run: | echo "Running NixOS module integration test..." - nix build .#checks.x86_64-linux.headscale -L --fallback + nix build .#checks.x86_64-linux.headscale -L diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 349ad53d1..f2eeacb4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,10 +7,6 @@ on: - "*" # triggers only if push new tag version workflow_dispatch: -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - jobs: goreleaser: if: github.repository == 'juanfont/headscale' @@ -21,6 +17,25 @@ jobs: with: fetch-depth: 0 + - name: Pin Docker to v28 (avoid v29 breaking changes) + run: | + # Docker 29 breaks docker build via Go client libraries and + # docker load/save with certain tarball formats. + # Pin to Docker 28.x until our tooling is updated. + # https://github.com/actions/runner-images/issues/13474 + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update -qq + VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}') + sudo apt-get install -y --allow-downgrades \ + "docker-ce=${VERSION}" "docker-ce-cli=${VERSION}" + sudo systemctl restart docker + docker version + - name: Login to DockerHub uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: @@ -34,10 +49,14 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Run goreleaser - run: goreleaser release --clean + run: nix develop --command -- goreleaser release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/servertest.yml b/.github/workflows/servertest.yml deleted file mode 100644 index 795b29590..000000000 --- a/.github/workflows/servertest.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Server Tests - -on: - push: - branches: - - main - pull_request: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -# hscontrol/servertest is excluded from the sandboxed gotest flake check: it is -# slow (10s+ convergence cases) and timing-sensitive (race/stress/HA property -# tests), so it runs here in the devShell with a generous timeout instead. -defaults: - run: - shell: nix develop --fallback --command bash -e {0} - -permissions: - contents: read - -jobs: - servertest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - - name: go test ./hscontrol/servertest - env: - CGO_ENABLED: "0" - run: go test -timeout=20m ./hscontrol/servertest/... diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index 0e2a03377..b30a5e1d5 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -6,15 +6,9 @@ on: [pull_request] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true -defaults: - run: - shell: nix develop --fallback --command bash -e {0} jobs: # build: Builds binaries and Docker images once, uploads as artifacts for reuse. # build-postgres: Pulls postgres image separately to avoid Docker Hub rate limits. - # build-tailscale-released: Pre-pulls released Tailscale images from ghcr.io - # so fork PRs (no DOCKERHUB_USERNAME secret) don't hit Docker Hub rate - # limits at test time. # sqlite: Runs all integration tests with SQLite backend. # postgres: Runs a subset of tests with PostgreSQL to verify database compatibility. build: @@ -39,18 +33,23 @@ jobs: - '.github/workflows/test-integration.yaml' - '.github/workflows/integration-test-template.yml' - 'Dockerfile.*' - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 if: steps.changed-files.outputs.files == 'true' - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - name: Build binaries and warm Go cache if: steps.changed-files.outputs.files == 'true' run: | # Build all Go binaries in one nix shell to maximize cache reuse - go build -o hi ./cmd/hi - CGO_ENABLED=0 GOOS=linux go build -o headscale ./cmd/headscale - # Build integration test binary to warm the cache with all dependencies - go test -c ./integration -o /dev/null 2>/dev/null || true + nix develop --command -- bash -c ' + go build -o hi ./cmd/hi + CGO_ENABLED=0 GOOS=linux go build -o headscale ./cmd/headscale + # Build integration test binary to warm the cache with all dependencies + go test -c ./integration -o /dev/null 2>/dev/null || true + ' - name: Upload hi binary if: steps.changed-files.outputs.files == 'true' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 @@ -70,26 +69,25 @@ jobs: name: go-cache path: go-cache.tar.gz retention-days: 10 - - name: Force overlay2 storage driver + - name: Pin Docker to v28 (avoid v29 breaking changes) if: steps.changed-files.outputs.files == 'true' run: | - # Docker 29 runner images default to overlayfs, which breaks - # docker build via Go SDK libraries and docker save/load - # tarball formats. overlay2 is the long-standing default. + # Docker 29 breaks docker build via Go client libraries and + # docker load/save with certain tarball formats. + # Pin to Docker 28.x until our tooling is updated. # https://github.com/actions/runner-images/issues/13474 - sudo mkdir -p /etc/docker - echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update -qq + VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}') + sudo apt-get install -y --allow-downgrades \ + "docker-ce=${VERSION}" "docker-ce-cli=${VERSION}" sudo systemctl restart docker docker version - - name: Login to Docker Hub - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} - if: env.DOCKERHUB_USERNAME != '' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ env.DOCKERHUB_TOKEN }} - name: Build headscale image if: steps.changed-files.outputs.files == 'true' run: | @@ -125,24 +123,25 @@ jobs: needs: build if: needs.build.outputs.files-changed == 'true' steps: - - name: Force overlay2 storage driver - shell: bash + - name: Pin Docker to v28 (avoid v29 breaking changes) run: | - sudo mkdir -p /etc/docker - echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json + # Docker 29 breaks docker build via Go client libraries and + # docker load/save with certain tarball formats. + # Pin to Docker 28.x until our tooling is updated. + # https://github.com/actions/runner-images/issues/13474 + sudo install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update -qq + VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}') + sudo apt-get install -y --allow-downgrades \ + "docker-ce=${VERSION}" "docker-ce-cli=${VERSION}" sudo systemctl restart docker docker version - - name: Login to Docker Hub - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} - if: env.DOCKERHUB_USERNAME != '' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ env.DOCKERHUB_TOKEN }} - name: Pull and save postgres image - shell: bash run: | docker pull postgres:latest docker tag postgres:latest postgres:${{ github.sha }} @@ -153,67 +152,8 @@ jobs: name: postgres-image path: postgres-image.tar.gz retention-days: 10 - build-tailscale-released: - runs-on: ubuntu-24.04-arm - needs: build - if: needs.build.outputs.files-changed == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: NixOS/nix-installer-action@6b8548fe06acfb0155a50ab5d561accb215764cc # main - - uses: Mic92/hestia/action@ff07bb902a9968ac0c3d0e51d90a606662a375d8 # main - - name: Force overlay2 storage driver - run: | - sudo mkdir -p /etc/docker - echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json - sudo systemctl restart docker - docker version - - name: Login to Docker Hub - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }} - if: env.DOCKERHUB_USERNAME != '' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ env.DOCKERHUB_TOKEN }} - - name: List Tailscale versions to pre-pull - id: versions - run: | - versions=$(go run ./cmd/hi list-versions --set=must --exclude=head) - echo "versions=${versions}" >> "$GITHUB_OUTPUT" - echo "Pre-pulling: ${versions}" - - name: Pull Tailscale images - run: | - # Releases come from ghcr.io (anonymous, unmetered). The - # "unstable" floating tag on ghcr.io has been stale since 2022, - # so it still needs to come from Docker Hub. xargs -P 0 fans - # out one process per tag and returns non-zero if any pull - # fails. - refs="" - for v in ${{ steps.versions.outputs.versions }}; do - if [ "${v}" = "unstable" ]; then - refs="${refs} tailscale/tailscale:${v}" - else - refs="${refs} ghcr.io/tailscale/tailscale:${v}" - fi - done - echo "${refs}" | tr ' ' '\n' | grep -v '^$' \ - | xargs -P 0 -I{} docker pull "{}" - echo "REFS=${refs}" >> "$GITHUB_ENV" - - name: Save Tailscale images to tarball - run: | - # Single docker save with all refs: one consistent snapshot, no - # parallel-daemon race. - docker save ${REFS} | gzip > tailscale-released-images.tar.gz - ls -lh tailscale-released-images.tar.gz - - name: Upload Tailscale released images - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - name: tailscale-released-images - path: tailscale-released-images.tar.gz - retention-days: 10 sqlite: - needs: [build, build-tailscale-released] + needs: build if: needs.build.outputs.files-changed == 'true' strategy: fail-fast: false @@ -241,7 +181,7 @@ jobs: - TestACLDynamicUnknownUserRemoval - TestAPIAuthenticationBypass - TestAPIAuthenticationBypassCurl - - TestRemoteCLIAuthenticationBypass + - TestGRPCAuthenticationBypass - TestCLIWithConfigAuthenticationBypass - TestAuthKeyLogoutAndReloginSameUser - TestAuthKeyLogoutAndReloginNewUser @@ -262,34 +202,20 @@ jobs: - TestAuthWebFlowAuthenticationPingAll - TestAuthWebFlowLogoutAndReloginSameUser - TestAuthWebFlowLogoutAndReloginNewUser - - TestApiKeyCommand - - TestApiKeyCommandValidation - - TestAuthCommandValidation - - TestNodeCommand - - TestNodeExpireCommand - - TestNodeRenameCommand - - TestPreAuthKeyCorrectUserLoggedInCommand - - TestTaggedNodesCLIOutput - - TestNodeExpireFlagsCommand - - TestNodeCommandValidation - - TestNodeTagCommand - - TestNodeRouteCommands - - TestNodeBackfillIPsCommand - - TestOAuthClientCommand - - TestOAuthClientCommandValidation - TestPolicyCheckCommand - TestSSHTestsRejectFailingPolicy - - TestPolicyCommand - - TestPolicyBrokenConfigCommand + - TestUserCommand - TestPreAuthKeyCommand - TestPreAuthKeyCommandWithoutExpiry - TestPreAuthKeyCommandReusableEphemeral - - TestPreAuthKeyDeleteCommand - - TestPreAuthKeyCommandValidation - - TestServerInfoCommands - - TestUserCommand - - TestUserCreateCommand - - TestUserCommandValidation + - TestPreAuthKeyCorrectUserLoggedInCommand + - TestTaggedNodesCLIOutput + - TestApiKeyCommand + - TestNodeCommand + - TestNodeExpireCommand + - TestNodeRenameCommand + - TestPolicyCommand + - TestPolicyBrokenConfigCommand - TestDERPVerifyEndpoint - TestResolveMagicDNS - TestResolveMagicDNSExtraRecordsPath @@ -311,7 +237,6 @@ jobs: - Test2118DeletingOnlineNodePanics - TestGrantCapRelay - TestGrantCapDrive - - TestK8sOperator - TestEnablingRoutes - TestHASubnetRouterFailover - TestSubnetRouteACL @@ -344,7 +269,6 @@ jobs: - TestSSHCheckModeUnapprovedTimeout - TestSSHCheckModeCheckPeriodCLI - TestSSHCheckModeAutoApprove - - TestSSHCheckModeSessionLossReDelegates - TestSSHCheckModeNegativeCLI - TestSSHLocalpart - TestTagsAuthKeyWithTagRequestDifferentTag @@ -379,8 +303,6 @@ jobs: - TestTagsAuthKeyWithoutUserInheritsTags - TestTagsAuthKeyWithoutUserRejectsAdvertisedTags - TestTagsAuthKeyConvertToUserViaCLIRegister - - TestTS2021WebSocketGET - - TestTS2021WASMClientUnderNode - TestTailscaleRustAxum uses: ./.github/workflows/integration-test-template.yml secrets: inherit @@ -389,7 +311,7 @@ jobs: postgres_flag: "--postgres=0" database_name: "sqlite" postgres: - needs: [build, build-postgres, build-tailscale-released] + needs: [build, build-postgres] if: needs.build.outputs.files-changed == 'true' strategy: fail-fast: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..31eb431b4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Tests + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 2 + + - name: Get changed files + id: changed-files + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + files: + - '*.nix' + - 'go.*' + - '**/*.go' + - 'integration_test/' + - 'config-example.yaml' + + - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + if: steps.changed-files.outputs.files == 'true' + - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 + if: steps.changed-files.outputs.files == 'true' + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', + '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} + + - name: Run tests + if: steps.changed-files.outputs.files == 'true' + env: + # As of 2025-01-06, these env vars was not automatically + # set anymore which breaks the initdb for postgres on + # some of the database migration tests. + LC_ALL: "en_US.UTF-8" + LC_CTYPE: "en_US.UTF-8" + run: nix develop --command -- gotestsum diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml new file mode 100644 index 000000000..1c8b262ed --- /dev/null +++ b/.github/workflows/update-flake.yml @@ -0,0 +1,19 @@ +name: update-flake-lock +on: + workflow_dispatch: # allows manual triggering + schedule: + - cron: "0 0 * * 0" # runs weekly on Sunday at 00:00 + +jobs: + lockfile: + if: github.repository == 'juanfont/headscale' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3 # v17 + - name: Update flake.lock + uses: DeterminateSystems/update-flake-lock@428c2b58a4b7414dabd372acb6a03dba1084d3ab # v25 + with: + pr-title: "Update flake.lock" diff --git a/.gitignore b/.gitignore index 9f72ef80a..8dbdbe855 100644 --- a/.gitignore +++ b/.gitignore @@ -46,9 +46,6 @@ result integration_test/etc/config.dump.yaml -# OpenAPI spec is served live from the code and emitted on demand, not committed -/openapi/v1/headscale.yaml - # MkDocs .cache /site diff --git a/.golangci.yaml b/.golangci.yaml index 1721cf613..c6d8ff014 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -13,7 +13,6 @@ linters: - gochecknoinits - gocognit - godox - - gomodguard - interfacebloat - ireturn - lll @@ -31,17 +30,6 @@ linters: - wrapcheck - wsl settings: - goconst: - # Test fixtures repeat strings (IPs, tags, hostnames) by their - # nature; extracting them obscures the test rather than helping. - # Production code stays strict. - ignore-tests: true - # Default is 3. Bump so "happens thrice" cases that are not part - # of a shared vocabulary do not get extracted. - min-occurrences: 5 - # Default is 3. Short literals ("set", "get", "new") read better - # at call sites than behind a named constant. - min-len: 6 forbidigo: forbid: # Forbid time.Sleep everywhere with context-appropriate alternatives diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa4355c4c..c1b065670 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,9 +2,8 @@ # See: https://prek.j178.dev/quickstart/ # See: https://prek.j178.dev/builtin/ -# Global exclusions - ignore generated code (proto output and emitted OpenAPI) -# and recorded golden fixtures. -exclude: ^(gen|openapi)/|^hscontrol/testdata/apiv1_golden/ +# Global exclusions - ignore generated code +exclude: ^gen/ repos: # Built-in hooks from pre-commit/pre-commit-hooks diff --git a/AGENTS.md b/AGENTS.md index 8f3428acb..7fc325c6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -275,11 +275,6 @@ Key reminders: `e = e.Str("k", v)`. Forgetting to reassign silently drops the field. - **Tests**: prefer `hscontrol/servertest/` for server-level tests that don't need Docker — faster than full integration tests. -- **View types in read paths**: response serializers must read through - `NodeView`/`UserView`/`PreAuthKeyView` accessors. `AsStruct()` clones the - whole record on every read — it is only for DB-write/merge clones and mutable - working copies, never to build an API response. `grep AsStruct hscontrol/api` - must come back empty. ## Gotchas diff --git a/CHANGELOG.md b/CHANGELOG.md index 785d7d855..9bb650027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,71 +1,8 @@ # CHANGELOG -## 0.30.0 (202x-xx-xx) +## 0.29.0 (202x-xx-xx) -**Minimum supported Tailscale client version: v1.xx.0** - -### v1 REST API replaced; gRPC and Protobuf removed - -The v1 REST API now provides an OpenAPI 3.1 specification at -`/api/v1/openapi.yaml`, with interactive documentation at `/api/v1/docs`. This -replaces the Swagger 2.0 document and the `/swagger` UI. The Protobuf, gRPC and -grpc-gateway stack behind it is gone, and the `headscale` CLI now talks to the -HTTP API directly. - -[#3324](https://github.com/juanfont/headscale/pull/3324) - -### OAuth clients and scopes for the v2 API - -The v2 API now authenticates with OAuth 2.0 client-credentials, the way the -Tailscale ecosystem does. An OAuth client mints short-lived access tokens whose -scopes limit which operations they may perform and whose tags limit the devices -they may create, so a credential can be issued with only the access it needs. -The `headscale oauth-clients` command manages them. This lets the Tailscale -Terraform provider and Kubernetes operator drive Headscale unchanged; admin API -keys remain all-access. - -[#3334](https://github.com/juanfont/headscale/pull/3334) - -### BREAKING - -#### API - -- The gRPC API is removed; all programmatic access now goes through the HTTP API at `/api/v1` [#3324](https://github.com/juanfont/headscale/pull/3324) -- API errors are now RFC 7807 `application/problem+json`, including authentication failures, instead of the previous gRPC-status JSON shape [#3324](https://github.com/juanfont/headscale/pull/3324) -- Errors that previously returned HTTP 500 — unknown users or nodes, malformed input, duplicate names — now return the correct 404, 400 or 409 [#3324](https://github.com/juanfont/headscale/pull/3324) -- The OpenAPI document is OpenAPI 3.1 at `/api/v1/openapi.yaml` (docs at `/api/v1/docs`), replacing Swagger 2.0 at `/swagger` [#3324](https://github.com/juanfont/headscale/pull/3324) - -#### CLI - -- `--output json` / `--output yaml` now emit the API's shape — camelCase fields, string-encoded IDs, RFC3339 timestamps — instead of the old Protobuf encoding [#3324](https://github.com/juanfont/headscale/pull/3324) -- `headscale policy` renames the database-bypass flag from `--bypass-grpc-and-access-database-directly` to `--bypass-server-and-access-database-directly` [#3324](https://github.com/juanfont/headscale/pull/3324) - -### Changes - -- Expiring or deleting a non-existent pre-auth key now returns an error instead of silently succeeding [#3324](https://github.com/juanfont/headscale/pull/3324) -- Improve systemd service file hardening [#3341](https://github.com/juanfont/headscale/pull/3341) - -## 0.29.2 (2026-07-01) - -**Minimum supported Tailscale client version: v1.80.0** - -### Changes - -- Fix map generation serializing on the policy lock, so a mass reconnect on `autogroup:self`, via or relay policies no longer stalls clients into `unexpected EOF` retry loops [#3358](https://github.com/juanfont/headscale/pull/3358) -- Fix `/ts2021` rejecting the WebSocket `GET` upgrade with 405, which prevented Tailscale JS/WASM control clients from connecting [#3359](https://github.com/juanfont/headscale/pull/3359) -- Gracefully handle nodes with an invalid FQDN (empty or too long) instead of failing map delivery; offending names are logged at startup with the fix command [#3349](https://github.com/juanfont/headscale/pull/3349) - -## 0.29.1 (2026-06-18) - -**Minimum supported Tailscale client version: v1.80.0** - -### Changes - -- Fix nodes with `tags='null'` losing their assigned user on upgrade [#3325](https://github.com/juanfont/headscale/pull/3325) - -## 0.29.0 (2026-06-17) - -**Minimum supported Tailscale client version: v1.80.0** +**Minimum supported Tailscale client version: v1.76.0** ### Tailscale ACL compatibility improvements @@ -124,15 +61,12 @@ policy and reload. This feature is **beta** while behavioural coverage against Tailscale SaaS broadens. -[#3263](https://github.com/juanfont/headscale/pull/3263) - ### SSH rule validation SSH rule parsing now trims surrounding whitespace on `action`, `users`, `src`, and `dst`, rejects empty or wildcard entries in `users`, rejects empty `acceptEnv`, and rejects negative `checkPeriod`. `hosts:` aliases are rejected as SSH destinations, non-ASCII tag names are rejected at parse time, and the wording for group-nesting cycles matches Tailscale SaaS. -[#3263](https://github.com/juanfont/headscale/pull/3263) ### Grants @@ -143,9 +77,9 @@ field steers traffic through specific tagged subnet routers or exit nodes. The ` an ACL rule. Grants can be mixed with ACLs in the same policy file. [#2180](https://github.com/juanfont/headscale/pull/2180) -As part of this, we added `autogroup:danger-all`. It resolves to `0.0.0.0/0` and `::/0`, all IP +As part of this, we added `autogroup:danger-all`. It resolves to `0.0.0.0/0` and `::/0` — all IP addresses, including those outside the tailnet. This replaces the old behaviour where `*` matched -all IPs (see BREAKING below). The name is intentional: accepting traffic from the entire +all IPs (see BREAKING below). The name is intentionally scary: accepting traffic from the entire internet is a security-sensitive choice. `autogroup:danger-all` can only be used as a source. ### Node attributes (`nodeAttrs`) @@ -169,7 +103,7 @@ Frequently requested capabilities this unlocks include `magicdns-aaaa`, `disable-relay-server`, `disable-captive-portal-detection`, `nextdns:` / `nextdns:no-device-info`, `randomize-client-port`, and the Taildrive `drive:share` / `drive:access` pair. The set is not -limited to these, any string-only cap an operator places in policy +limited to these — any string-only cap an operator places in policy reaches clients unchanged. `randomizeClientPort` also lands as a top-level policy field that toggles @@ -216,9 +150,28 @@ mode: A wildcard `nodeAttrs` (`"target": ["*"]`) hands the caps to every node when fine-grained control is not needed. -### Hostname sanitisation +### Hostname handling (cleanroom rewrite) -Hostnames are now santised using Tailscales `magicdns` sanitisation rules, matching Tailscale SaaS behavior. This means that hostnames with non-ASCII characters, special characters, or reserved DNS label characters are now transformed into valid DNS labels for MagicDNS. This improves our previously too strict sanitisation that rejected hostnames based on our guesswork and not based on the Tailscale upstream behaviour. +The hostname ingest pipeline has been rewritten to match Tailscale SaaS byte-for-byte. +Headscale previously had three overlapping regexes and two disagreeing entry points +(registration vs map-request update), which caused a recurring class of bugs: names +containing apostrophes, spaces, dots, or non-ASCII characters were alternately rejected +(dropping updates with log spam) or stored as `invalid-` surrogates +([#3188](https://github.com/juanfont/headscale/issues/3188), +[#2926](https://github.com/juanfont/headscale/issues/2926), +[#2343](https://github.com/juanfont/headscale/issues/2343), +[#2762](https://github.com/juanfont/headscale/issues/2762), +[#2177](https://github.com/juanfont/headscale/issues/2177), +[#2121](https://github.com/juanfont/headscale/issues/2121), +[#2449](https://github.com/juanfont/headscale/issues/2449), +[#363](https://github.com/juanfont/headscale/issues/363)). + +What changed: + +- Sanitisation and validation now come directly from + `tailscale.com/util/dnsname.SanitizeHostname` / `ValidLabel`. +- Admin rename (`headscale nodes rename`) now validates via `dnsname.ValidLabel` and + rejects labels already held by another node (previously coerced invalid input silently). Examples that previously regressed and now work: @@ -231,24 +184,11 @@ Examples that previously regressed and now work: | `My-PC!` | `My-PC!` | `my-pc` | | `我的电脑` | `我的电脑` | `node` | -[#3202](https://github.com/juanfont/headscale/pull/3202) - -### HA subnet router health probing - -Headscale now actively probes HA subnet routers to detect nodes that are connected but not -forwarding traffic. The control plane periodically pings HA subnet routers via the Noise -control channel and fails over to a healthy standby if the primary stops responding. This is -enabled by default (`node.routes.ha.probe_interval: 10s`, `probe_timeout: 5s`) and only -active when HA routes exist (2+ nodes advertising the same prefix). Set `probe_interval` to -`0` to disable. This complements the existing disconnect-based failover, catching "zombie -connected" routers that maintain their control session but cannot route packets. -[#3194](https://github.com/juanfont/headscale/pull/3194) - ### BREAKING #### Hostname handling -- The `GivenName` collision policy changed from an 8-char random hash suffix (`laptop-abc12xyz`) to a monotonic numeric suffix (`laptop`, `laptop-1`, `laptop-2`, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal `node` instead of `invalid-`. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw `Hostname` column is unchanged. [#3202](https://github.com/juanfont/headscale/pull/3202) +- The `GivenName` collision policy changed from an 8-char random hash suffix (`laptop-abc12xyz`) to a monotonic numeric suffix (`laptop`, `laptop-1`, `laptop-2`, …), matching Tailscale SaaS. Empty / all-non-ASCII hostnames now fall back to the literal `node` instead of `invalid-`. MagicDNS names change on upgrade for any node whose previous label was a random-suffix form; the raw `Hostname` column is unchanged. #### ACL Policy @@ -257,8 +197,8 @@ connected" routers that maintain their control session but cannot route packets. - Policies that need to match all IP addresses including non-Tailscale IPs should use `autogroup:danger-all` as a source, or explicit CIDR ranges as destinations [#2180](https://github.com/juanfont/headscale/pull/2180) - `autogroup:danger-all` can only be used as a source; it cannot be used as a destination - **Note**: Users with non-standard IP ranges configured in `prefixes.ipv4` or `prefixes.ipv6` (which is unsupported and produces a warning) will need to explicitly specify their CIDR ranges in ACL rules instead of using `*` -- Validate `autogroup:self` source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for `autogroup:self` destinations [#3036](https://github.com/juanfont/headscale/pull/3036) - - Policies using tags, hosts, or IP addresses as sources for `autogroup:self` destinations will now fail validation +- Validate autogroup:self source restrictions matching Tailscale behavior - tags, hosts, and IPs are rejected as sources for autogroup:self destinations [#3036](https://github.com/juanfont/headscale/pull/3036) + - Policies using tags, hosts, or IP addresses as sources for autogroup:self destinations will now fail validation - The `proto:icmp` protocol name now only includes ICMPv4 (protocol 1), matching Tailscale behavior [#3036](https://github.com/juanfont/headscale/pull/3036) - Previously, `proto:icmp` included both ICMPv4 and ICMPv6 - Use `proto:ipv6-icmp` or protocol number `58` explicitly for ICMPv6 @@ -274,9 +214,10 @@ connected" routers that maintain their control session but cannot route packets. - The `randomize_client_port` server-config key was removed; the toggle now lives in the policy file as a top-level - `randomizeClientPort` field, matching the Tailscale-hosted schema. [#3251](https://github.com/juanfont/headscale/pull/3251) + `randomizeClientPort` field, matching the Tailscale-hosted schema. Headscale refuses to start when the old key is set. Move it to the - policy file referenced by `policy.path`: + policy file referenced by `policy.path` (defaults to + `/etc/headscale/policy.hujson`): ```jsonc { @@ -296,6 +237,16 @@ connected" routers that maintain their control session but cannot route packets. - `headscale nodes register` is deprecated in favour of `headscale auth register --auth-id --user ` [#1850](https://github.com/juanfont/headscale/pull/1850) - The old command continues to work but will be removed in a future release +### HA subnet router health probing + +Headscale now actively probes HA subnet routers to detect nodes that are connected but not +forwarding traffic. The control plane periodically pings HA subnet routers via the Noise +control channel and fails over to a healthy standby if the primary stops responding. This is +enabled by default (`node.routes.ha.probe_interval: 10s`, `probe_timeout: 5s`) and only +active when HA routes exist (2+ nodes advertising the same prefix). Set `probe_interval` to +`0` to disable. This complements the existing disconnect-based failover, catching "zombie +connected" routers that maintain their control session but cannot route packets. + ### Changes #### ACL Policy @@ -336,7 +287,7 @@ connected" routers that maintain their control session but cannot route packets. - `headscale policy check --bypass-grpc-and-access-database-directly` validates `user@` tokens against the live user database [#3160](https://github.com/juanfont/headscale/issues/3160) - Remove deprecated `--namespace` flag from `nodes list`, `nodes register`, and `debug create-node` commands (use `--user` instead) [#3093](https://github.com/juanfont/headscale/pull/3093) - Remove deprecated `namespace`/`ns` command aliases for `users` and `machine`/`machines` aliases for `nodes` [#3093](https://github.com/juanfont/headscale/pull/3093) -- Fix `DestroyUser` deleting all pre-auth keys in the database instead of only the target user's keys [#3155](https://github.com/juanfont/headscale/pull/3155) +- **User deletion**: Fix `DestroyUser` deleting all pre-auth keys in the database instead of only the target user's keys [#3155](https://github.com/juanfont/headscale/pull/3155) - `headscale policy check` evaluates the `tests` block when invoked with `--bypass-grpc-and-access-database-directly`; without the flag it warns instead of running the tests against empty data [#1803](https://github.com/juanfont/headscale/issues/1803) #### API @@ -356,7 +307,6 @@ connected" routers that maintain their control session but cannot route packets. - Tagged nodes (registered with tagged pre-auth keys) are exempt from default expiry - `oidc.expiry` has been removed; use `node.expiry` instead (applies to all registration methods including OIDC) - `ephemeral_node_inactivity_timeout` is deprecated in favour of `node.ephemeral.inactivity_timeout` -- Add `trusted_proxies` to gate `True-Client-IP` / `X-Real-IP` / `X-Forwarded-For` (previously honoured from any client) [#3268](https://github.com/juanfont/headscale/pull/3268) #### Debug @@ -368,10 +318,8 @@ connected" routers that maintain their control session but cannot route packets. - Remove old migrations for the debian package [#3185](https://github.com/juanfont/headscale/pull/3185) - Install `config-example.yaml` as example for the debian package [#3186](https://github.com/juanfont/headscale/pull/3186) -- Fix user-owned re-registration with zero client expiry and no default storing `0001-01-01 00:00:00` in the database instead of `NULL` [#3199](https://github.com/juanfont/headscale/pull/3199) -- Fix `tailscaled` restart on a node with no expiry resetting `NULL` to `0001-01-01 00:00:00` in the database, affecting both tagged and untagged nodes [#3197](https://github.com/juanfont/headscale/pull/3197) -- Backfill `nodes.expiry` rows persisted by older versions as `0001-01-01 00:00:00` to `NULL`, so nodes upgraded from <0.28 stop reporting as expired [#3284](https://github.com/juanfont/headscale/issues/3284) -- Update reverse proxy documentation for `trusted_proxies` configuration option [#3292](https://github.com/juanfont/headscale/pull/3292) +- **Node Expiry**: Fix user owned re registration with zero client expiry and no default storing `0001-01-01 00:00:00` in the database instead of NULL [#3199](https://github.com/juanfont/headscale/pull/3199) + - Pre-existing rows with `0001-01-01 00:00:00` are not backfilled; they clear themselves the next time the node re-registers ## 0.28.0 (2026-02-04) diff --git a/Dockerfile.derper b/Dockerfile.derper index 166d38547..0deb69e40 100644 --- a/Dockerfile.derper +++ b/Dockerfile.derper @@ -1,6 +1,6 @@ # For testing purposes only -FROM golang:1.26.4-alpine AS build-env +FROM golang:1.26.3-alpine AS build-env WORKDIR /go/src @@ -12,7 +12,7 @@ WORKDIR /go/src/tailscale ARG TARGETARCH RUN GOARCH=$TARGETARCH go install -v ./cmd/derper -FROM alpine:3.23 +FROM alpine:3.22 RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl COPY --from=build-env /go/bin/* /usr/local/bin/ diff --git a/Dockerfile.integration b/Dockerfile.integration index 670ef452c..c897ab8d2 100644 --- a/Dockerfile.integration +++ b/Dockerfile.integration @@ -2,7 +2,7 @@ # and are in no way endorsed by Headscale's maintainers as an # official nor supported release or distribution. -FROM docker.io/golang:1.26.4-trixie AS builder +FROM docker.io/golang:1.26.2-trixie AS builder ARG VERSION=dev ENV GOPATH /go WORKDIR /go/src/headscale diff --git a/Dockerfile.tailscale-HEAD b/Dockerfile.tailscale-HEAD index b3151e614..7d0d43bfc 100644 --- a/Dockerfile.tailscale-HEAD +++ b/Dockerfile.tailscale-HEAD @@ -4,7 +4,7 @@ # This Dockerfile is more or less lifted from tailscale/tailscale # to ensure a similar build process when testing the HEAD of tailscale. -FROM golang:1.26.4-alpine AS build-env +FROM golang:1.26.3-alpine AS build-env WORKDIR /go/src @@ -36,7 +36,7 @@ RUN GOARCH=$TARGETARCH go install -tags="${BUILD_TAGS}" -ldflags="\ -X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \ -v ./cmd/tailscale ./cmd/tailscaled ./cmd/containerboot -FROM alpine:3.23 +FROM alpine:3.22 # Upstream: ca-certificates ip6tables iptables iproute2 # Tests: curl python3 (traceroute via BusyBox) RUN apk add --no-cache ca-certificates curl ip6tables iptables iproute2 python3 diff --git a/Dockerfile.tailscale-rs b/Dockerfile.tailscale-rs index 043764771..c60409238 100644 --- a/Dockerfile.tailscale-rs +++ b/Dockerfile.tailscale-rs @@ -1,4 +1,4 @@ -FROM rust:1.95-trixie AS builder +FROM rust:1.94-bookworm AS builder ARG TAILSCALE_RS_REPO=https://github.com/tailscale/tailscale-rs.git ARG TAILSCALE_RS_REF=main @@ -15,7 +15,7 @@ RUN sed -i '/^axum = \["dep:axum"\]/a insecure-keyfetch = ["ts_control/insecure- RUN cargo build --release --features axum,insecure-keyfetch --example axum -FROM debian:trixie-slim +FROM debian:bookworm-slim RUN apt-get update && \ apt-get install -y --no-install-recommends \ diff --git a/Dockerfile.wasmclient b/Dockerfile.wasmclient deleted file mode 100644 index cc939a09f..000000000 --- a/Dockerfile.wasmclient +++ /dev/null @@ -1,30 +0,0 @@ -# For integration testing only. -# -# Builds the Tailscale control client (integration/wasmic/wasmclient) for -# GOOS=js/GOARCH=wasm and packages it with Go's wasm_exec Node runner. The -# container idles; the integration test execs -# node /app/wasm_exec_node.js /app/client.wasm -# to drive a real browser-style WebSocket GET against headscale's /ts2021, -# guarding the regression in issue #3357. - -FROM golang:1.26.4-alpine AS build - -WORKDIR /src - -# Only the module metadata and the wasm client package are needed to build the -# js/wasm binary; its imports (tailscale.com/control/controlhttp, ...) resolve -# from the module proxy. -COPY go.mod go.sum ./ -COPY integration/wasmic/wasmclient ./integration/wasmic/wasmclient - -RUN GOOS=js GOARCH=wasm go build -o /out/client.wasm ./integration/wasmic/wasmclient \ - && cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" /out/wasm_exec.js \ - && cp "$(go env GOROOT)/lib/wasm/wasm_exec_node.js" /out/wasm_exec_node.js - -FROM node:24-alpine - -WORKDIR /app -COPY --from=build /out/ /app/ - -# Idle; the test execs the client on demand with the headscale control URL. -ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/Makefile b/Makefile index da2726714..6a2f84584 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ endef # Source file collections using shell find for better performance GO_SOURCES := $(shell find . -name '*.go' -not -path './gen/*' -not -path './vendor/*') +PROTO_SOURCES := $(shell find . -name '*.proto' -not -path './gen/*' -not -path './vendor/*') PRETTIER_SOURCES := $(shell find . \( -name '*.md' -o -name '*.yaml' -o -name '*.yml' -o -name '*.ts' -o -name '*.js' -o -name '*.html' -o -name '*.css' -o -name '*.scss' -o -name '*.sass' \) -not -path './gen/*' -not -path './vendor/*' -not -path './node_modules/*') # Default target @@ -34,6 +35,8 @@ check-deps: $(call check_tool,gofumpt) $(call check_tool,mdformat) $(call check_tool,prettier) + $(call check_tool,clang-format) + $(call check_tool,buf) # Build targets .PHONY: build @@ -50,7 +53,7 @@ test: check-deps $(GO_SOURCES) go.mod go.sum # Formatting targets .PHONY: fmt -fmt: fmt-go fmt-mdformat fmt-prettier +fmt: fmt-go fmt-mdformat fmt-prettier fmt-proto .PHONY: fmt-go fmt-go: check-deps $(GO_SOURCES) @@ -68,49 +71,35 @@ fmt-prettier: check-deps $(PRETTIER_SOURCES) @echo "Formatting markup and config files..." prettier --write '**/*.{ts,js,md,yaml,yml,sass,css,scss,html}' +.PHONY: fmt-proto +fmt-proto: check-deps $(PROTO_SOURCES) + @echo "Formatting Protocol Buffer files..." + clang-format -i $(PROTO_SOURCES) + # Linting targets .PHONY: lint -lint: lint-go +lint: lint-go lint-proto .PHONY: lint-go lint-go: check-deps $(GO_SOURCES) go.mod go.sum @echo "Linting Go code..." golangci-lint run --timeout 10m +.PHONY: lint-proto +lint-proto: check-deps $(PROTO_SOURCES) + @echo "Linting Protocol Buffer files..." + cd proto/ && buf lint + # Code generation .PHONY: generate generate: check-deps @echo "Generating code..." go generate ./... - $(MAKE) client - -# Emit the OpenAPI spec on demand. The server serves it live at /openapi.yaml; -# this is for external consumers or inspection and is not committed. -.PHONY: openapi -openapi: - @echo "Emitting OpenAPI spec from code..." - go run ./cmd/gen-openapi - -# Generate the strongly-typed Go HTTP clients (v1 and v2). The served specs are -# OpenAPI 3.1, but oapi-codegen v2 does not yet read 3.1, so each client is -# generated from a transient 3.0.3 downgrade of its document. Pinned so the -# committed clients are reproducible. -.PHONY: client -client: - @echo "Generating API clients..." - @tmp=$$(mktemp -t headscale-openapi-3.0.XXXXXX.yaml); \ - go run ./cmd/gen-openapi -downgrade "$$tmp" && \ - go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.7.1 \ - -generate types,client -package clientv1 -o gen/client/v1/client.gen.go "$$tmp" && \ - go run ./cmd/gen-openapi -api v2 -downgrade "$$tmp" && \ - go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.7.1 \ - -generate types,client -package clientv2 -o gen/client/v2/client.gen.go "$$tmp"; \ - status=$$?; rm -f "$$tmp"; exit $$status # Clean targets .PHONY: clean clean: - rm -rf headscale gen/client + rm -rf headscale gen # Development workflow .PHONY: dev @@ -130,9 +119,9 @@ help: @echo " all - Run lint, test, and build (default)" @echo " build - Build headscale binary" @echo " test - Run Go tests" - @echo " fmt - Format all code (Go, docs, markup)" - @echo " lint - Lint all code (Go)" - @echo " generate - Generate code (go generate + client)" + @echo " fmt - Format all code (Go, docs, proto)" + @echo " lint - Lint all code (Go, proto)" + @echo " generate - Generate code from Protocol Buffers" @echo " dev - Full development workflow (fmt + lint + test + build)" @echo " clean - Clean build artifacts" @echo "" @@ -140,7 +129,9 @@ help: @echo " fmt-go - Format Go code only" @echo " fmt-mdformat - Format documentation only" @echo " fmt-prettier - Format markup and config files only" + @echo " fmt-proto - Format Protocol Buffer files only" @echo " lint-go - Lint Go code only" + @echo " lint-proto - Lint Protocol Buffer files only" @echo "" @echo "Dependencies:" @echo " check-deps - Verify required tools are available" diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 000000000..d7b832ab3 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,21 @@ +version: v1 +plugins: + - name: go + out: gen/go + opt: + - paths=source_relative + - name: go-grpc + out: gen/go + opt: + - paths=source_relative + - name: grpc-gateway + out: gen/go + opt: + - paths=source_relative + - generate_unbound_methods=true + # - name: gorm + # out: gen/go + # opt: + # - paths=source_relative,enums=string,gateway=true + - name: openapiv2 + out: gen/openapiv2 diff --git a/cmd/dev/README.md b/cmd/dev/README.md index f703f4421..46da415db 100644 --- a/cmd/dev/README.md +++ b/cmd/dev/README.md @@ -42,7 +42,8 @@ go tool mts node1 status | `--port` | 8080 | Headscale listen port | | `--keep` | false | Keep state directory on exit | -The metrics/debug port is `port + 1010` (default 9090). +The metrics/debug port is `port + 1010` (default 9090) and the gRPC +port is `port + 42363` (default 50443). ## What it does diff --git a/cmd/dev/main.go b/cmd/dev/main.go index a1953123b..b3c7a8407 100644 --- a/cmd/dev/main.go +++ b/cmd/dev/main.go @@ -28,14 +28,17 @@ var errHealthTimeout = errors.New("health check timed out") var errEmptyAuthKey = errors.New("empty auth key in response") -// maxDevPort is the highest --port value that keeps the derived metrics -// port (port+1010) inside the valid 1..65535 TCP range. -const maxDevPort = 64525 +// maxDevPort is the highest --port value that keeps both the derived +// metrics port (port+1010) and gRPC port (port+42363) inside the valid +// 1..65535 TCP range. +const maxDevPort = 23172 const devConfig = `--- server_url: http://127.0.0.1:%d listen_addr: 127.0.0.1:%d metrics_listen_addr: 127.0.0.1:%d +grpc_listen_addr: 127.0.0.1:%d +grpc_allow_insecure: true noise: private_key_path: %s/noise_private.key @@ -80,7 +83,7 @@ func main() { if *port < 1 || *port > maxDevPort { log.Fatalf( - "--port must be in 1..%d (higher values overflow the derived metrics port); got %d", + "--port must be in 1..%d (higher values overflow the derived gRPC port); got %d", maxDevPort, *port, ) } @@ -98,6 +101,7 @@ func main() { func run() error { metricsPort := *port + 1010 // default 9090 + grpcPort := *port + 42363 // default 50443 tmpDir, err := os.MkdirTemp("", "headscale-dev-") if err != nil { @@ -110,9 +114,8 @@ func run() error { // Write config. configPath := filepath.Join(tmpDir, "config.yaml") - configContent := fmt.Sprintf( - devConfig, - *port, *port, metricsPort, + configContent := fmt.Sprintf(devConfig, + *port, *port, metricsPort, grpcPort, tmpDir, tmpDir, tmpDir, ) @@ -190,8 +193,7 @@ func run() error { } // Print banner. - fmt.Printf( - ` + fmt.Printf(` === Headscale Dev Environment === Server: http://127.0.0.1:%d Metrics: http://127.0.0.1:%d diff --git a/cmd/gen-openapi/main.go b/cmd/gen-openapi/main.go deleted file mode 100644 index 32568f14f..000000000 --- a/cmd/gen-openapi/main.go +++ /dev/null @@ -1,75 +0,0 @@ -// Command gen-openapi emits a Headscale OpenAPI document from the authoritative -// Huma definitions in hscontrol/api/v1 and hscontrol/api/v2. The server also -// serves each spec live (at /openapi.yaml and /api/v2/openapi); this tool emits -// them on demand, and with -downgrade the 3.0.3 form used to generate the typed -// client. The output is not committed. -// -// Usage: -// -// go run ./cmd/gen-openapi # write the v1 3.1 spec to its default path -// go run ./cmd/gen-openapi -api v2 # write the v2 3.1 spec to its default path -// go run ./cmd/gen-openapi -downgrade # write the v1 3.0.3 downgrade (for client gen) -// go run ./cmd/gen-openapi -api v2 -downgrade # the v2 3.0.3 downgrade -package main - -import ( - "flag" - "log" - "os" - "path/filepath" - - apiv1 "github.com/juanfont/headscale/hscontrol/api/v1" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" -) - -// spec bundles a version's full (3.1) and downgraded (3.0.3) generators with the -// committed output path. outPath is relative to the repository root. -type spec struct { - full func() ([]byte, error) - down func() ([]byte, error) - outPath string -} - -// specs maps the -api value to its generators. -var specs = map[string]spec{ - "v1": {apiv1.Spec, apiv1.Spec30, "openapi/v1/headscale.yaml"}, - "v2": {apiv2.Spec, apiv2.Spec30, "openapi/v2/headscale.yaml"}, -} - -func main() { - api := flag.String("api", "v1", "which API spec to emit: v1 or v2") - downgrade := flag.String("downgrade", "", "write the OpenAPI 3.0.3 downgrade to this path instead of the committed 3.1 spec") - flag.Parse() - - s, ok := specs[*api] - if !ok { - log.Fatalf("unknown -api %q (want v1 or v2)", *api) - } - - if *downgrade != "" { - writeSpec(*downgrade, s.down) - - return - } - - writeSpec(s.outPath, s.full) -} - -func writeSpec(path string, gen func() ([]byte, error)) { - spec, err := gen() - if err != nil { - log.Fatalf("generating OpenAPI spec: %v", err) - } - - err = os.MkdirAll(filepath.Dir(path), 0o755) - if err != nil { - log.Fatalf("creating output directory: %v", err) - } - - err = os.WriteFile(path, spec, 0o600) - if err != nil { - log.Fatalf("writing %s: %v", path, err) - } - - log.Printf("wrote %s", path) -} diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go index 82559235a..acc9ffa9f 100644 --- a/cmd/headscale/cli/api_key.go +++ b/cmd/headscale/cli/api_key.go @@ -3,11 +3,11 @@ package cli import ( "context" "fmt" - "net/http" "strconv" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/util" + "github.com/pterm/pterm" "github.com/spf13/cobra" ) @@ -41,43 +41,36 @@ var apiKeysCmd = &cobra.Command{ } var listAPIKeys = &cobra.Command{ - Use: cmdList, + Use: "list", Short: "List the Api keys for headscale", - Aliases: []string{"ls", cmdShow}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - resp, err := client.ListApiKeysWithResponse(ctx) + Aliases: []string{"ls", "show"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + response, err := client.ListApiKeys(ctx, &v1.ListApiKeysRequest{}) if err != nil { return fmt.Errorf("listing api keys: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } + return printListOutput(cmd, response.GetApiKeys(), func() error { + tableData := pterm.TableData{ + {"ID", "Prefix", "Expiration", "Created"}, + } - apiKeys := resp.JSON200.ApiKeys - - return printListOutput(cmd, apiKeys, func() error { - rows := make([][]string, 0, len(apiKeys)) - for _, key := range apiKeys { + for _, key := range response.GetApiKeys() { expiration := "-" - if key.Expiration != nil { - expiration = ColourTime(*key.Expiration) + + if key.GetExpiration() != nil { + expiration = ColourTime(key.GetExpiration().AsTime()) } - var created string - if key.CreatedAt != nil { - created = key.CreatedAt.Format(HeadscaleDateTimeFormat) - } - - rows = append(rows, []string{ - key.Id, - key.Prefix, + tableData = append(tableData, []string{ + strconv.FormatUint(key.GetId(), util.Base10), + key.GetPrefix(), expiration, - created, + key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat), }) } - return renderTable([]string{"ID", "Prefix", colExpiration, colCreated}, rows) + return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() }) }), } @@ -89,25 +82,21 @@ var createAPIKeyCmd = &cobra.Command{ Creates a new Api key, the Api key is only visible on creation and cannot be retrieved again. If you lose a key, create a new one and revoke (expire) the old one.`, - Aliases: []string{"c", cmdNew}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - expiryTime, err := expirationFromFlag(cmd) + Aliases: []string{"c", "new"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + expiration, err := expirationFromFlag(cmd) if err != nil { return err } - resp, err := client.CreateApiKeyWithResponse(ctx, clientv1.CreateApiKeyJSONRequestBody{ - Expiration: &expiryTime, + response, err := client.CreateApiKey(ctx, &v1.CreateApiKeyRequest{ + Expiration: expiration, }) if err != nil { return fmt.Errorf("creating api key: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.ApiKey, resp.JSON200.ApiKey) + return printOutput(cmd, response.GetApiKey(), response.GetApiKey()) }), } @@ -128,96 +117,45 @@ func apiKeyIDOrPrefix(cmd *cobra.Command) (uint64, string, error) { } var expireAPIKeyCmd = &cobra.Command{ - Use: cmdExpire, + Use: "expire", Short: "Expire an ApiKey", - Aliases: []string{"revoke", aliasExp, "e"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"revoke", "exp", "e"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { id, prefix, err := apiKeyIDOrPrefix(cmd) if err != nil { return err } - body := clientv1.ExpireApiKeyJSONRequestBody{} - - if id != 0 { - idStr := strconv.FormatUint(id, util.Base10) - body.Id = &idStr - } - - if prefix != "" { - body.Prefix = &prefix - } - - resp, err := client.ExpireApiKeyWithResponse(ctx, body) + response, err := client.ExpireApiKey(ctx, &v1.ExpireApiKeyRequest{ + Id: id, + Prefix: prefix, + }) if err != nil { return fmt.Errorf("expiring api key: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Key expired") + return printOutput(cmd, response, "Key expired") }), } var deleteAPIKeyCmd = &cobra.Command{ - Use: cmdDelete, + Use: "delete", Short: "Delete an ApiKey", - Aliases: []string{"remove", aliasDel}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"remove", "del"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { id, prefix, err := apiKeyIDOrPrefix(cmd) if err != nil { return err } - // The DELETE route addresses the key by its prefix in the path. When the - // user deletes by --id we resolve the id to its (masked) prefix first, - // since the path segment is required and a query-only id cannot be routed. - if prefix == "" { - prefix, err = apiKeyPrefixForID(ctx, client, id) - if err != nil { - return err - } - } - - resp, err := client.DeleteApiKeyWithResponse(ctx, prefix, &clientv1.DeleteApiKeyParams{}) + response, err := client.DeleteApiKey(ctx, &v1.DeleteApiKeyRequest{ + Id: id, + Prefix: prefix, + }) if err != nil { return fmt.Errorf("deleting api key: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Key deleted") + return printOutput(cmd, response, "Key deleted") }), } - -// apiKeyPrefixForID resolves an API key id to its display prefix by listing the -// keys. The DELETE endpoint addresses keys by prefix in the URL path, so a -// delete by --id needs the prefix; the returned masked prefix is accepted by -// the server's lookup. -func apiKeyPrefixForID( - ctx context.Context, - client *clientv1.ClientWithResponses, - id uint64, -) (string, error) { - resp, err := client.ListApiKeysWithResponse(ctx) - if err != nil { - return "", fmt.Errorf("listing api keys: %w", err) - } - - if resp.StatusCode() != http.StatusOK { - return "", apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - idStr := strconv.FormatUint(id, util.Base10) - for _, key := range resp.JSON200.ApiKeys { - if key.Id == idStr { - return key.Prefix, nil - } - } - - return "", fmt.Errorf("%w: api key %d not found", errMissingParameter, id) -} diff --git a/cmd/headscale/cli/auth.go b/cmd/headscale/cli/auth.go index 30abe797a..8a5476dd0 100644 --- a/cmd/headscale/cli/auth.go +++ b/cmd/headscale/cli/auth.go @@ -3,9 +3,8 @@ package cli import ( "context" "fmt" - "net/http" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/spf13/cobra" ) @@ -34,66 +33,61 @@ var authCmd = &cobra.Command{ var authRegisterCmd = &cobra.Command{ Use: "register", Short: "Register a node to your network", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { user, _ := cmd.Flags().GetString("user") authID, _ := cmd.Flags().GetString("auth-id") - resp, err := client.AuthRegisterWithResponse(ctx, clientv1.AuthRegisterJSONRequestBody{ - AuthId: &authID, - User: &user, - }) + request := &v1.AuthRegisterRequest{ + AuthId: authID, + User: user, + } + + response, err := client.AuthRegister(ctx, request) if err != nil { return fmt.Errorf("registering node: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - node := resp.JSON200.Node - return printOutput( cmd, - node, - fmt.Sprintf("Node %s registered", node.GivenName), - ) + response.GetNode(), + fmt.Sprintf("Node %s registered", response.GetNode().GetGivenName())) }), } var authApproveCmd = &cobra.Command{ Use: "approve", Short: "Approve a pending authentication request", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { authID, _ := cmd.Flags().GetString("auth-id") - resp, err := client.AuthApproveWithResponse(ctx, clientv1.AuthApproveJSONRequestBody{AuthId: &authID}) + request := &v1.AuthApproveRequest{ + AuthId: authID, + } + + response, err := client.AuthApprove(ctx, request) if err != nil { return fmt.Errorf("approving auth request: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Auth request approved") + return printOutput(cmd, response, "Auth request approved") }), } var authRejectCmd = &cobra.Command{ Use: "reject", Short: "Reject a pending authentication request", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { authID, _ := cmd.Flags().GetString("auth-id") - resp, err := client.AuthRejectWithResponse(ctx, clientv1.AuthRejectJSONRequestBody{AuthId: &authID}) + request := &v1.AuthRejectRequest{ + AuthId: authID, + } + + response, err := client.AuthReject(ctx, request) if err != nil { return fmt.Errorf("rejecting auth request: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Auth request rejected") + return printOutput(cmd, response, "Auth request rejected") }), } diff --git a/cmd/headscale/cli/debug.go b/cmd/headscale/cli/debug.go index 64262c00d..1f934f0bb 100644 --- a/cmd/headscale/cli/debug.go +++ b/cmd/headscale/cli/debug.go @@ -3,9 +3,8 @@ package cli import ( "context" "fmt" - "net/http" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/types" "github.com/spf13/cobra" ) @@ -33,7 +32,7 @@ var debugCmd = &cobra.Command{ var createNodeCmd = &cobra.Command{ Use: "create-node", Short: "Create a node that can be registered with `auth register <>` command", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { user, _ := cmd.Flags().GetString("user") name, _ := cmd.Flags().GetString("name") registrationID, _ := cmd.Flags().GetString("key") @@ -45,20 +44,18 @@ var createNodeCmd = &cobra.Command{ routes, _ := cmd.Flags().GetStringSlice("route") - resp, err := client.DebugCreateNodeWithResponse(ctx, clientv1.DebugCreateNodeJSONRequestBody{ - Key: ®istrationID, - Name: &name, - User: &user, - Routes: &routes, - }) + request := &v1.DebugCreateNodeRequest{ + Key: registrationID, + Name: name, + User: user, + Routes: routes, + } + + response, err := client.DebugCreateNode(ctx, request) if err != nil { return fmt.Errorf("creating node: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.Node, "Node created") + return printOutput(cmd, response.GetNode(), "Node created") }), } diff --git a/cmd/headscale/cli/health.go b/cmd/headscale/cli/health.go index 6fccb0ac2..b3b4f4307 100644 --- a/cmd/headscale/cli/health.go +++ b/cmd/headscale/cli/health.go @@ -3,9 +3,8 @@ package cli import ( "context" "fmt" - "net/http" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/spf13/cobra" ) @@ -17,16 +16,12 @@ var healthCmd = &cobra.Command{ Use: "health", Short: "Check the health of the Headscale server", Long: "Check the health of the Headscale server. This command will return an exit code of 0 if the server is healthy, or 1 if it is not.", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - resp, err := client.HealthWithResponse(ctx) + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + response, err := client.Health(ctx, &v1.HealthRequest{}) if err != nil { return fmt.Errorf("checking health: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "") + return printOutput(cmd, response, "") }), } diff --git a/cmd/headscale/cli/mockoidc.go b/cmd/headscale/cli/mockoidc.go index 255143bae..be951b762 100644 --- a/cmd/headscale/cli/mockoidc.go +++ b/cmd/headscale/cli/mockoidc.go @@ -3,7 +3,6 @@ package cli import ( "context" "encoding/json" - "errors" "fmt" "net" "net/http" @@ -17,14 +16,18 @@ import ( "github.com/spf13/cobra" ) -var ( - errMockOidcClientIDNotDefined = errors.New("MOCKOIDC_CLIENT_ID not defined") - errMockOidcClientSecretNotDefined = errors.New("MOCKOIDC_CLIENT_SECRET not defined") - errMockOidcPortNotDefined = errors.New("MOCKOIDC_PORT not defined") - errMockOidcUsersNotDefined = errors.New("MOCKOIDC_USERS not defined") -) +// Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors +type Error string -const refreshTTL = 60 * time.Minute +func (e Error) Error() string { return string(e) } + +const ( + errMockOidcClientIDNotDefined = Error("MOCKOIDC_CLIENT_ID not defined") + errMockOidcClientSecretNotDefined = Error("MOCKOIDC_CLIENT_SECRET not defined") + errMockOidcPortNotDefined = Error("MOCKOIDC_PORT not defined") + errMockOidcUsersNotDefined = Error("MOCKOIDC_USERS not defined") + refreshTTL = 60 * time.Minute +) var accessTTL = 2 * time.Minute diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 9dc798a3a..ec24b7f90 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -3,17 +3,17 @@ package cli import ( "context" "fmt" - "net/http" "net/netip" "strconv" "strings" "time" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/util" "github.com/pterm/pterm" "github.com/samber/lo" "github.com/spf13/cobra" + "google.golang.org/protobuf/types/known/timestamppb" "tailscale.com/types/key" ) @@ -67,59 +67,41 @@ var registerNodeCmd = &cobra.Command{ Use: "register", Short: "Registers a node to your network", Deprecated: "use 'headscale auth register --auth-id --user ' instead", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { user, _ := cmd.Flags().GetString("user") registrationID, _ := cmd.Flags().GetString("key") - params := &clientv1.RegisterNodeParams{ - User: &user, - Key: ®istrationID, + request := &v1.RegisterNodeRequest{ + Key: registrationID, + User: user, } - resp, err := client.RegisterNodeWithResponse(ctx, params) + response, err := client.RegisterNode(ctx, request) if err != nil { return fmt.Errorf("registering node: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - node := resp.JSON200.Node - return printOutput( cmd, - node, - fmt.Sprintf("Node %s registered", node.GivenName), - ) + response.GetNode(), + fmt.Sprintf("Node %s registered", response.GetNode().GetGivenName())) }), } var listNodesCmd = &cobra.Command{ - Use: cmdList, + Use: "list", Short: "List nodes", - Aliases: []string{"ls", cmdShow}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"ls", "show"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { user, _ := cmd.Flags().GetString("user") - params := &clientv1.ListNodesParams{} - if user != "" { - params.User = &user - } - - resp, err := client.ListNodesWithResponse(ctx, params) + response, err := client.ListNodes(ctx, &v1.ListNodesRequest{User: user}) if err != nil { return fmt.Errorf("listing nodes: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - nodes := resp.JSON200.Nodes - - return printListOutput(cmd, nodes, func() error { - tableData, err := nodesToPtables(nodes) + return printListOutput(cmd, response.GetNodes(), func() error { + tableData, err := nodesToPtables(user, response.GetNodes()) if err != nil { return fmt.Errorf("converting to table: %w", err) } @@ -133,33 +115,27 @@ var listNodeRoutesCmd = &cobra.Command{ Use: "list-routes", Short: "List routes available on nodes", Aliases: []string{"lsr", "routes"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") - resp, err := client.ListNodesWithResponse(ctx, &clientv1.ListNodesParams{}) + response, err := client.ListNodes(ctx, &v1.ListNodesRequest{}) if err != nil { return fmt.Errorf("listing nodes: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - nodes := resp.JSON200.Nodes - + nodes := response.GetNodes() if identifier != 0 { - idStr := strconv.FormatUint(identifier, util.Base10) - for _, node := range nodes { - if node.Id == idStr { - nodes = []clientv1.Node{node} + for _, node := range response.GetNodes() { + if node.GetId() == identifier { + nodes = []*v1.Node{node} break } } } - nodes = lo.Filter(nodes, func(n clientv1.Node, _ int) bool { - return len(n.SubnetRoutes) > 0 || len(n.ApprovedRoutes) > 0 || len(n.AvailableRoutes) > 0 + nodes = lo.Filter(nodes, func(n *v1.Node, _ int) bool { + return (n.GetSubnetRoutes() != nil && len(n.GetSubnetRoutes()) > 0) || (n.GetApprovedRoutes() != nil && len(n.GetApprovedRoutes()) > 0) || (n.GetAvailableRoutes() != nil && len(n.GetAvailableRoutes()) > 0) }) return printListOutput(cmd, nodes, func() error { @@ -169,33 +145,29 @@ var listNodeRoutesCmd = &cobra.Command{ } var expireNodeCmd = &cobra.Command{ - Use: cmdExpire, + Use: "expire", Short: "Expire (log out) a node in your network", Long: `Expiring a node will keep the node in the database and force it to reauthenticate. Use --disable to disable key expiry (node will never expire).`, - Aliases: []string{"logout", aliasExp, "e"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"logout", "exp", "e"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") disableExpiry, _ := cmd.Flags().GetBool("disable") - nodeID := strconv.FormatUint(identifier, util.Base10) // Handle disable expiry - node will never expire. if disableExpiry { - disable := true + request := &v1.ExpireNodeRequest{ + NodeId: identifier, + DisableExpiry: true, + } - resp, err := client.ExpireNodeWithResponse(ctx, nodeID, clientv1.ExpireNodeJSONRequestBody{ - DisableExpiry: &disable, - }) + response, err := client.ExpireNode(ctx, request) if err != nil { return fmt.Errorf("disabling node expiry: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.Node, "Node expiry disabled") + return printOutput(cmd, response.GetNode(), "Node expiry disabled") } expiry, _ := cmd.Flags().GetString("expiry") @@ -213,31 +185,28 @@ Use --disable to disable key expiry (node will never expire).`, } } - resp, err := client.ExpireNodeWithResponse(ctx, nodeID, clientv1.ExpireNodeJSONRequestBody{ - Expiry: &expiryTime, - }) + request := &v1.ExpireNodeRequest{ + NodeId: identifier, + Expiry: timestamppb.New(expiryTime), + } + + response, err := client.ExpireNode(ctx, request) if err != nil { return fmt.Errorf("expiring node: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - node := resp.JSON200.Node - if now.Equal(expiryTime) || now.After(expiryTime) { - return printOutput(cmd, node, "Node expired") + return printOutput(cmd, response.GetNode(), "Node expired") } - return printOutput(cmd, node, "Node expiration updated") + return printOutput(cmd, response.GetNode(), "Node expiration updated") }), } var renameNodeCmd = &cobra.Command{ Use: "rename NEW_NAME", Short: "Renames a node in your network", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") newName := "" @@ -245,55 +214,55 @@ var renameNodeCmd = &cobra.Command{ newName = args[0] } - resp, err := client.RenameNodeWithResponse(ctx, strconv.FormatUint(identifier, util.Base10), newName) + request := &v1.RenameNodeRequest{ + NodeId: identifier, + NewName: newName, + } + + response, err := client.RenameNode(ctx, request) if err != nil { return fmt.Errorf("renaming node: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.Node, "Node renamed") + return printOutput(cmd, response.GetNode(), "Node renamed") }), } var deleteNodeCmd = &cobra.Command{ - Use: cmdDelete, + Use: "delete", Short: "Delete a node", - Aliases: []string{aliasDel}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"del"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") - nodeID := strconv.FormatUint(identifier, util.Base10) - getResponse, err := client.GetNodeWithResponse(ctx, nodeID) + getRequest := &v1.GetNodeRequest{ + NodeId: identifier, + } + + getResponse, err := client.GetNode(ctx, getRequest) if err != nil { return fmt.Errorf("getting node: %w", err) } - if getResponse.StatusCode() != http.StatusOK { - return apiError(getResponse.StatusCode(), getResponse.ApplicationproblemJSONDefault) + deleteRequest := &v1.DeleteNodeRequest{ + NodeId: identifier, } if !confirmAction(cmd, fmt.Sprintf( "Do you want to remove the node %s?", - getResponse.JSON200.Node.Name, + getResponse.GetNode().GetName(), )) { - return printOutput(cmd, map[string]string{colResult: "Node not deleted"}, "Node not deleted") + return printOutput(cmd, map[string]string{"Result": "Node not deleted"}, "Node not deleted") } - deleteResponse, err := client.DeleteNodeWithResponse(ctx, nodeID) + _, err = client.DeleteNode(ctx, deleteRequest) if err != nil { return fmt.Errorf("deleting node: %w", err) } - if deleteResponse.StatusCode() != http.StatusOK { - return apiError(deleteResponse.StatusCode(), deleteResponse.ApplicationproblemJSONDefault) - } - return printOutput( cmd, - map[string]string{colResult: "Node deleted"}, + map[string]string{"Result": "Node deleted"}, "Node deleted", ) }), @@ -319,26 +288,26 @@ be assigned to nodes.`, return nil } - return withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error { - confirmed := true + ctx, client, conn, cancel, err := newHeadscaleCLIWithConfig() + if err != nil { + return fmt.Errorf("connecting to headscale: %w", err) + } + defer cancel() + defer conn.Close() - resp, err := client.BackfillNodeIPsWithResponse(ctx, &clientv1.BackfillNodeIPsParams{ - Confirmed: &confirmed, - }) - if err != nil { - return fmt.Errorf("backfilling IPs: %w", err) - } + changes, err := client.BackfillNodeIPs(ctx, &v1.BackfillNodeIPsRequest{Confirmed: true}) + if err != nil { + return fmt.Errorf("backfilling IPs: %w", err) + } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Node IPs backfilled successfully") - }) + return printOutput(cmd, changes, "Node IPs backfilled successfully") }, } -func nodesToPtables(nodes []clientv1.Node) (pterm.TableData, error) { +func nodesToPtables( + currentUser string, + nodes []*v1.Node, +) (pterm.TableData, error) { tableHeader := []string{ "ID", "Hostname", @@ -350,57 +319,88 @@ func nodesToPtables(nodes []clientv1.Node) (pterm.TableData, error) { "IP addresses", "Ephemeral", "Last seen", - colExpiration, + "Expiration", "Connected", "Expired", } - tableData := make(pterm.TableData, 1, 1+len(nodes)) - tableData[0] = tableHeader + tableData := pterm.TableData{tableHeader} for _, node := range nodes { - // An absent pre-auth key decodes into a zero NodePreAuthKey, so guard - // on Id before reading its flags. - ephemeral := node.PreAuthKey.Id != "" && node.PreAuthKey.Ephemeral - - var lastSeenTime string - if node.LastSeen != nil { - lastSeenTime = node.LastSeen.Format(HeadscaleDateTimeFormat) + var ephemeral bool + if node.GetPreAuthKey() != nil && node.GetPreAuthKey().GetEphemeral() { + ephemeral = true } - expiryTime := "N/A" - if node.Expiry != nil { - expiryTime = node.Expiry.Format(HeadscaleDateTimeFormat) + var ( + lastSeen time.Time + lastSeenTime string + ) + + if node.GetLastSeen() != nil { + lastSeen = node.GetLastSeen().AsTime() + lastSeenTime = lastSeen.Format(HeadscaleDateTimeFormat) + } + + var ( + expiry time.Time + expiryTime string + ) + + if node.GetExpiry() != nil { + expiry = node.GetExpiry().AsTime() + expiryTime = expiry.Format(HeadscaleDateTimeFormat) + } else { + expiryTime = "N/A" } var machineKey key.MachinePublic - err := machineKey.UnmarshalText([]byte(node.MachineKey)) + err := machineKey.UnmarshalText( + []byte(node.GetMachineKey()), + ) if err != nil { machineKey = key.MachinePublic{} } var nodeKey key.NodePublic - err = nodeKey.UnmarshalText([]byte(node.NodeKey)) + err = nodeKey.UnmarshalText( + []byte(node.GetNodeKey()), + ) if err != nil { return nil, err } - online := pterm.LightRed("offline") - if node.Online { + var online string + if node.GetOnline() { online = pterm.LightGreen("online") + } else { + online = pterm.LightRed("offline") } - expired := pterm.LightGreen("no") - if node.Expiry != nil && node.Expiry.Before(time.Now()) { + var expired string + if node.GetExpiry() != nil && node.GetExpiry().AsTime().Before(time.Now()) { expired = pterm.LightRed("yes") + } else { + expired = pterm.LightGreen("no") } - tags := strings.Join(node.Tags, "\n") + var tagsBuilder strings.Builder + + for _, tag := range node.GetTags() { + tagsBuilder.WriteString("\n" + tag) + } + + tags := strings.TrimLeft(tagsBuilder.String(), "\n") + + var user string + if node.GetUser() != nil { + user = node.GetUser().GetName() + } var ipBuilder strings.Builder - for _, addr := range node.IpAddresses { + for _, addr := range node.GetIpAddresses() { ip, err := netip.ParseAddr(addr) if err == nil { if ipBuilder.Len() > 0 { @@ -414,12 +414,12 @@ func nodesToPtables(nodes []clientv1.Node) (pterm.TableData, error) { ipAddresses := ipBuilder.String() nodeData := []string{ - node.Id, - node.Name, - node.GivenName, + strconv.FormatUint(node.GetId(), util.Base10), + node.GetName(), + node.GetGivenName(), machineKey.ShortString(), nodeKey.ShortString(), - node.User.Name, + user, tags, ipAddresses, strconv.FormatBool(ephemeral), @@ -438,7 +438,7 @@ func nodesToPtables(nodes []clientv1.Node) (pterm.TableData, error) { } func nodeRoutesToPtables( - nodes []clientv1.Node, + nodes []*v1.Node, ) pterm.TableData { tableHeader := []string{ "ID", @@ -447,16 +447,15 @@ func nodeRoutesToPtables( "Available", "Serving (Primary)", } - tableData := make(pterm.TableData, 1, 1+len(nodes)) - tableData[0] = tableHeader + tableData := pterm.TableData{tableHeader} for _, node := range nodes { nodeData := []string{ - node.Id, - node.GivenName, - strings.Join(node.ApprovedRoutes, "\n"), - strings.Join(node.AvailableRoutes, "\n"), - strings.Join(node.SubnetRoutes, "\n"), + strconv.FormatUint(node.GetId(), util.Base10), + node.GetGivenName(), + strings.Join(node.GetApprovedRoutes(), "\n"), + strings.Join(node.GetAvailableRoutes(), "\n"), + strings.Join(node.GetSubnetRoutes(), "\n"), } tableData = append( tableData, @@ -471,43 +470,43 @@ var tagCmd = &cobra.Command{ Use: "tag", Short: "Manage the tags of a node", Aliases: []string{"tags", "t"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") tagsToSet, _ := cmd.Flags().GetStringSlice("tags") - resp, err := client.SetTagsWithResponse(ctx, strconv.FormatUint(identifier, util.Base10), clientv1.SetTagsJSONRequestBody{ - Tags: &tagsToSet, - }) + // Sending tags to node + request := &v1.SetTagsRequest{ + NodeId: identifier, + Tags: tagsToSet, + } + + resp, err := client.SetTags(ctx, request) if err != nil { return fmt.Errorf("setting tags: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.Node, "Node updated") + return printOutput(cmd, resp.GetNode(), "Node updated") }), } var approveRoutesCmd = &cobra.Command{ Use: "approve-routes", Short: "Manage the approved routes of a node", - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { identifier, _ := cmd.Flags().GetUint64("identifier") routes, _ := cmd.Flags().GetStringSlice("routes") - resp, err := client.SetApprovedRoutesWithResponse(ctx, strconv.FormatUint(identifier, util.Base10), clientv1.SetApprovedRoutesJSONRequestBody{ - Routes: &routes, - }) + // Sending routes to node + request := &v1.SetApprovedRoutesRequest{ + NodeId: identifier, + Routes: routes, + } + + resp, err := client.SetApprovedRoutes(ctx, request) if err != nil { return fmt.Errorf("setting approved routes: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.Node, "Node updated") + return printOutput(cmd, resp.GetNode(), "Node updated") }), } diff --git a/cmd/headscale/cli/oauth_client.go b/cmd/headscale/cli/oauth_client.go deleted file mode 100644 index aeb566469..000000000 --- a/cmd/headscale/cli/oauth_client.go +++ /dev/null @@ -1,271 +0,0 @@ -package cli - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "net" - "net/http" - "strings" - - clientv2 "github.com/juanfont/headscale/gen/client/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/spf13/cobra" -) - -// oauthTailnet is the single Headscale tailnet the v2 API addresses as "-". -const oauthTailnet = "-" - -func init() { - rootCmd.AddCommand(oauthClientsCmd) - - oauthClientsCmd.AddCommand(listOAuthClientsCmd) - - createOAuthClientCmd.Flags(). - StringArrayP("scope", "s", nil, "Scope the client's tokens are granted (repeatable): auth_keys, oauth_keys, devices:core, devices:routes, policy_file, feature_settings (each with a :read variant), or all/all:read") - createOAuthClientCmd.Flags(). - StringArrayP("tag", "t", nil, "Tag the client's tokens may assign to devices (repeatable), e.g. tag:k8s-operator") - createOAuthClientCmd.Flags().StringP("description", "d", "", "Human-readable description") - oauthClientsCmd.AddCommand(createOAuthClientCmd) - - deleteOAuthClientCmd.Flags().StringP("id", "i", "", "OAuth client id") - oauthClientsCmd.AddCommand(deleteOAuthClientCmd) -} - -var oauthClientsCmd = &cobra.Command{ - Use: "oauth-clients", - Short: "Manage OAuth clients", - Aliases: []string{"oauth-client", "oauthclients", "oauthclient", "oauth"}, -} - -var createOAuthClientCmd = &cobra.Command{ - Use: "create", - Short: "Create an OAuth client", - Long: `Create a general-purpose OAuth client. It authenticates with the OAuth 2.0 -client-credentials grant and mints short-lived, scope-limited access tokens. -The wire format is compatible with Tailscale tooling (the Terraform provider, -the Kubernetes operator, tscli, ...), so those can drive Headscale unchanged. - -The client secret is shown ONCE on creation and cannot be retrieved again; if -you lose it, delete the client and create a new one. - -Scopes gate what the client's tokens may do; tags are the device tags those -tokens may assign and are required when the scopes include devices:core or -auth_keys.`, - Aliases: []string{"c", cmdNew}, - RunE: func(cmd *cobra.Command, _ []string) error { - scopes, _ := cmd.Flags().GetStringArray("scope") - tags, _ := cmd.Flags().GetStringArray("tag") - description, _ := cmd.Flags().GetString("description") - - if len(scopes) == 0 { - return fmt.Errorf("at least one --scope is required: %w", errMissingParameter) - } - - ctx, client, cancel, err := newV2Client() - if err != nil { - return err - } - defer cancel() - - keyType := "client" - - resp, err := client.CreateKeyWithResponse(ctx, oauthTailnet, clientv2.CreateKeyRequest{ - KeyType: &keyType, - Scopes: &scopes, - Tags: &tags, - Description: &description, - }) - if err != nil { - return fmt.Errorf("creating oauth client: %w", err) - } - - err = v2Error(resp.HTTPResponse.StatusCode, resp.Body) - if err != nil { - return err - } - - key := resp.JSON200 - - return printOutput(cmd, key, - fmt.Sprintf("OAuth client %s created.\nSecret (shown once, store it now): %s", key.Id, ptrStr(key.Key))) - }, -} - -var listOAuthClientsCmd = &cobra.Command{ - Use: cmdList, - Short: "List OAuth clients", - Aliases: []string{"ls", cmdShow}, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, client, cancel, err := newV2Client() - if err != nil { - return err - } - defer cancel() - - resp, err := client.ListKeysWithResponse(ctx, oauthTailnet, nil) - if err != nil { - return fmt.Errorf("listing oauth clients: %w", err) - } - - err = v2Error(resp.HTTPResponse.StatusCode, resp.Body) - if err != nil { - return err - } - - // The keys endpoint is multiplexed; keep only OAuth clients. - clients := make([]clientv2.Key, 0, len(resp.JSON200.Keys)) - - for _, k := range resp.JSON200.Keys { - if k.KeyType == "client" { - clients = append(clients, k) - } - } - - return printListOutput(cmd, clients, func() error { - rows := make([][]string, 0, len(clients)) - for _, c := range clients { - rows = append(rows, []string{ - c.Id, - strings.Join(ptrStrs(c.Scopes), ","), - strings.Join(ptrStrs(c.Tags), ","), - ptrStr(c.Description), - c.Created.Format(HeadscaleDateTimeFormat), - }) - } - - return renderTable([]string{"ID", "Scopes", "Tags", "Description", colCreated}, rows) - }) - }, -} - -var deleteOAuthClientCmd = &cobra.Command{ - Use: cmdDelete, - Short: "Delete an OAuth client", - Aliases: []string{"remove", aliasDel}, - RunE: func(cmd *cobra.Command, _ []string) error { - id, _ := cmd.Flags().GetString("id") - if id == "" { - return fmt.Errorf("--id is required: %w", errMissingParameter) - } - - ctx, client, cancel, err := newV2Client() - if err != nil { - return err - } - defer cancel() - - resp, err := client.DeleteKeyWithResponse(ctx, oauthTailnet, id) - if err != nil { - return fmt.Errorf("deleting oauth client: %w", err) - } - - err = v2Error(resp.HTTPResponse.StatusCode, resp.Body) - if err != nil { - return err - } - - return printOutput(cmd, map[string]string{"id": id}, "OAuth client "+id+" deleted") - }, -} - -// newV2Client builds a generated v2 API client, selecting the transport the same -// way the v1 client does: over the local unix socket it is unauthenticated -// (local trust); a remote address injects the configured API key as a bearer -// token. -func newV2Client() (context.Context, *clientv2.ClientWithResponses, context.CancelFunc, error) { - cfg, err := types.LoadCLIConfig() - if err != nil { - return nil, nil, nil, fmt.Errorf("loading configuration: %w", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), cfg.CLI.Timeout) - - if cfg.CLI.Address == "" { - socketPath := cfg.UnixSocket - - httpClient := &http.Client{Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialHeadscaleSocket(ctx, socketPath) - }, - }} - - client, err := clientv2.NewClientWithResponses("http://local", clientv2.WithHTTPClient(httpClient)) - if err != nil { - cancel() - - return nil, nil, nil, err - } - - return ctx, client, cancel, nil - } - - if cfg.CLI.APIKey == "" { - cancel() - - return nil, nil, nil, errAPIKeyNotSet - } - - transport := &http.Transport{} - if cfg.CLI.Insecure { - //nolint:gosec // intentionally honouring the insecure flag - transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } - - apiKey := cfg.CLI.APIKey - - client, err := clientv2.NewClientWithResponses( - clientBaseURL(cfg.CLI.Address), - clientv2.WithHTTPClient(&http.Client{Transport: transport}), - clientv2.WithRequestEditorFn(func(_ context.Context, req *http.Request) error { - req.Header.Set("Authorization", "Bearer "+apiKey) - - return nil - }), - ) - if err != nil { - cancel() - - return nil, nil, nil, err - } - - return ctx, client, cancel, nil -} - -// v2Error turns a non-2xx v2 response into an error. The v2 API emits the -// Tailscale error body ({"message":...}) rather than RFC 7807, so it reads the -// "message" field instead of the generated problem+json types. -func v2Error(status int, body []byte) error { - if status >= http.StatusOK && status < http.StatusMultipleChoices { - return nil - } - - var e struct { - Message string `json:"message"` - } - - if json.Unmarshal(body, &e) == nil && e.Message != "" { - //nolint:err113 // surfacing the server's message - return fmt.Errorf("api error (%d): %s", status, e.Message) - } - - //nolint:err113 // surfacing the server's body - return fmt.Errorf("api error (%d): %s", status, strings.TrimSpace(string(body))) -} - -func ptrStr(s *string) string { - if s == nil { - return "" - } - - return *s -} - -func ptrStrs(s *[]string) []string { - if s == nil { - return nil - } - - return *s -} diff --git a/cmd/headscale/cli/policy.go b/cmd/headscale/cli/policy.go index ae69cc8a9..145093e1d 100644 --- a/cmd/headscale/cli/policy.go +++ b/cmd/headscale/cli/policy.go @@ -1,13 +1,11 @@ package cli import ( - "context" "errors" "fmt" - "net/http" "os" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/types" @@ -16,13 +14,14 @@ import ( ) const ( - bypassFlag = "bypass-server-and-access-database-directly" //nolint:gosec // not a credential + bypassFlag = "bypass-grpc-and-access-database-directly" //nolint:gosec // not a credential ) var errAborted = errors.New("command aborted by user") -// bypassDatabase opens the database directly, bypassing the running server. -// The caller must close the returned handle. +// bypassDatabase loads the server config and opens the database directly, +// bypassing the gRPC server. The caller is responsible for closing the +// returned database handle. func bypassDatabase() (*db.HSDatabase, error) { cfg, err := types.LoadServerConfig() if err != nil { @@ -37,29 +36,19 @@ func bypassDatabase() (*db.HSDatabase, error) { return d, nil } -// openBypassDB confirms the destructive bypass action and opens the database -// directly. The caller is responsible for closing the returned handle. -func openBypassDB(cmd *cobra.Command) (*db.HSDatabase, error) { - if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { - return nil, errAborted - } - - return bypassDatabase() -} - func init() { rootCmd.AddCommand(policyCmd) - getPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing the API and does not require the server to be running") + getPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing gRPC and does not require the server to be running") policyCmd.AddCommand(getPolicy) setPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format") - setPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing the API and does not require the server to be running") + setPolicy.Flags().BoolP(bypassFlag, "", false, "Uses the headscale config to directly access the database, bypassing gRPC and does not require the server to be running") mustMarkRequired(setPolicy, "file") policyCmd.AddCommand(setPolicy) checkPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format") - checkPolicy.Flags().BoolP(bypassFlag, "", false, "Open the database directly (no running server required) to resolve user references and to evaluate the policy's tests and sshTests blocks. Required when those checks are needed.") + checkPolicy.Flags().BoolP(bypassFlag, "", false, "Open the database directly (no gRPC, no running server) to resolve user references and to evaluate the policy's tests and sshTests blocks. Required when those checks are needed.") mustMarkRequired(checkPolicy, "file") policyCmd.AddCommand(checkPolicy) } @@ -72,12 +61,16 @@ var policyCmd = &cobra.Command{ var getPolicy = &cobra.Command{ Use: "get", Short: "Print the current ACL Policy", - Aliases: []string{cmdShow, "view", "fetch"}, + Aliases: []string{"show", "view", "fetch"}, RunE: func(cmd *cobra.Command, args []string) error { var policyData string if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { - d, err := openBypassDB(cmd) + if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { + return errAborted + } + + d, err := bypassDatabase() if err != nil { return err } @@ -90,23 +83,19 @@ var getPolicy = &cobra.Command{ policyData = pol.Data } else { - err := withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error { - resp, err := client.GetPolicyWithResponse(ctx) - if err != nil { - return fmt.Errorf("loading ACL policy: %w", err) - } - - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - policyData = resp.JSON200.Policy - - return nil - }) + ctx, client, conn, cancel, err := newHeadscaleCLIWithConfig() if err != nil { - return err + return fmt.Errorf("connecting to headscale: %w", err) } + defer cancel() + defer conn.Close() + + response, err := client.GetPolicy(ctx, &v1.GetPolicyRequest{}) + if err != nil { + return fmt.Errorf("loading ACL policy: %w", err) + } + + policyData = response.GetPolicy() } // This does not pass output format as we don't support yaml, json or @@ -133,13 +122,17 @@ var setPolicy = &cobra.Command{ } if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { - d, err := openBypassDB(cmd) + if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { + return errAborted + } + + d, err := bypassDatabase() if err != nil { return err } defer d.Close() - users, err := d.ListUsers(nil) + users, err := d.ListUsers() if err != nil { return fmt.Errorf("loading users for policy validation: %w", err) } @@ -154,24 +147,18 @@ var setPolicy = &cobra.Command{ return fmt.Errorf("setting ACL policy: %w", err) } } else { - policyStr := string(policyBytes) + request := &v1.SetPolicyRequest{Policy: string(policyBytes)} - err := withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error { - resp, err := client.SetPolicyWithResponse(ctx, clientv1.SetPolicyJSONRequestBody{ - Policy: &policyStr, - }) - if err != nil { - return fmt.Errorf("setting ACL policy: %w", err) - } - - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return nil - }) + ctx, client, conn, cancel, err := newHeadscaleCLIWithConfig() if err != nil { - return err + return fmt.Errorf("connecting to headscale: %w", err) + } + defer cancel() + defer conn.Close() + + _, err = client.SetPolicy(ctx, request) + if err != nil { + return fmt.Errorf("setting ACL policy: %w", err) } } @@ -186,8 +173,8 @@ var checkPolicy = &cobra.Command{ Short: "Check the Policy file for errors", Long: ` Check validates the policy against the server's live users and nodes, - running any "tests" or "sshTests" block. By default the command calls a - running headscale over its API; pass --` + bypassFlag + ` to + running any "tests" or "sshTests" block. By default the command is a + thin frontend for a gRPC call to a running headscale; pass --` + bypassFlag + ` to open the database directly when headscale is not running.`, RunE: func(cmd *cobra.Command, args []string) error { policyPath, _ := cmd.Flags().GetString("file") @@ -198,13 +185,17 @@ var checkPolicy = &cobra.Command{ } if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { - d, err := openBypassDB(cmd) + if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { + return errAborted + } + + d, err := bypassDatabase() if err != nil { return err } defer d.Close() - users, err := d.ListUsers(nil) + users, err := d.ListUsers() if err != nil { return fmt.Errorf("loading users: %w", err) } @@ -214,9 +205,9 @@ var checkPolicy = &cobra.Command{ return fmt.Errorf("loading nodes: %w", err) } - // [policy.NewPolicyManager] validates structure and user references + // NewPolicyManager validates structure and user references // but intentionally skips test evaluation (boot path). - // [policy.PolicyManager.SetPolicy] is the user-write boundary and is what runs the + // SetPolicy is the user-write boundary and is what runs the // tests and sshTests blocks. pm, err := policy.NewPolicyManager(policyBytes, users, nodes.ViewSlice()) if err != nil { @@ -233,22 +224,14 @@ var checkPolicy = &cobra.Command{ return nil } - policyStr := string(policyBytes) + ctx, client, conn, cancel, err := newHeadscaleCLIWithConfig() + if err != nil { + return fmt.Errorf("connecting to headscale: %w", err) + } + defer cancel() + defer conn.Close() - err = withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error { - resp, err := client.CheckPolicyWithResponse(ctx, clientv1.CheckPolicyJSONRequestBody{ - Policy: &policyStr, - }) - if err != nil { - return err - } - - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return nil - }) + _, err = client.CheckPolicy(ctx, &v1.CheckPolicyRequest{Policy: string(policyBytes)}) if err != nil { return err } diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go index f6d4ad2b0..0a0f82852 100644 --- a/cmd/headscale/cli/preauthkeys.go +++ b/cmd/headscale/cli/preauthkeys.go @@ -3,12 +3,12 @@ package cli import ( "context" "fmt" - "net/http" "strconv" "strings" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/util" + "github.com/pterm/pterm" "github.com/spf13/cobra" ) @@ -42,57 +42,57 @@ var preauthkeysCmd = &cobra.Command{ } var listPreAuthKeys = &cobra.Command{ - Use: cmdList, + Use: "list", Short: "List all preauthkeys", - Aliases: []string{"ls", cmdShow}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - resp, err := client.ListPreAuthKeysWithResponse(ctx) + Aliases: []string{"ls", "show"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + response, err := client.ListPreAuthKeys(ctx, &v1.ListPreAuthKeysRequest{}) if err != nil { return fmt.Errorf("listing preauthkeys: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } + return printListOutput(cmd, response.GetPreAuthKeys(), func() error { + tableData := pterm.TableData{ + { + "ID", + "Key/Prefix", + "Reusable", + "Ephemeral", + "Used", + "Expiration", + "Created", + "Owner", + }, + } - preAuthKeys := resp.JSON200.PreAuthKeys - - return printListOutput(cmd, preAuthKeys, func() error { - rows := make([][]string, 0, len(preAuthKeys)) - for _, key := range preAuthKeys { - expiration := ColourTime(key.Expiration) - - owner := "-" - - switch { - case len(key.AclTags) > 0: - owner = strings.Join(key.AclTags, "\n") - case key.User.Id != "": - owner = key.User.Name + for _, key := range response.GetPreAuthKeys() { + expiration := "-" + if key.GetExpiration() != nil { + expiration = ColourTime(key.GetExpiration().AsTime()) } - rows = append(rows, []string{ - key.Id, - key.Key, - strconv.FormatBool(key.Reusable), - strconv.FormatBool(key.Ephemeral), - strconv.FormatBool(key.Used), + var owner string + if len(key.GetAclTags()) > 0 { + owner = strings.Join(key.GetAclTags(), "\n") + } else if key.GetUser() != nil { + owner = key.GetUser().GetName() + } else { + owner = "-" + } + + tableData = append(tableData, []string{ + strconv.FormatUint(key.GetId(), util.Base10), + key.GetKey(), + strconv.FormatBool(key.GetReusable()), + strconv.FormatBool(key.GetEphemeral()), + strconv.FormatBool(key.GetUsed()), expiration, - key.CreatedAt.Format(HeadscaleDateTimeFormat), + key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat), owner, }) } - return renderTable([]string{ - "ID", - "Key/Prefix", - "Reusable", - "Ephemeral", - "Used", - colExpiration, - colCreated, - "Owner", - }, rows) + return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() }) }), } @@ -100,103 +100,79 @@ var listPreAuthKeys = &cobra.Command{ var createPreAuthKeyCmd = &cobra.Command{ Use: "create", Short: "Creates a new preauthkey", - Aliases: []string{"c", cmdNew}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + Aliases: []string{"c", "new"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { user, _ := cmd.Flags().GetUint64("user") reusable, _ := cmd.Flags().GetBool("reusable") ephemeral, _ := cmd.Flags().GetBool("ephemeral") tags, _ := cmd.Flags().GetStringSlice("tags") - expiryTime, err := expirationFromFlag(cmd) + expiration, err := expirationFromFlag(cmd) if err != nil { return err } - userStr := strconv.FormatUint(user, util.Base10) - - request := clientv1.CreatePreAuthKeyJSONRequestBody{ - User: &userStr, - Reusable: &reusable, - Ephemeral: &ephemeral, - AclTags: &tags, - Expiration: &expiryTime, + request := &v1.CreatePreAuthKeyRequest{ + User: user, + Reusable: reusable, + Ephemeral: ephemeral, + AclTags: tags, + Expiration: expiration, } - resp, err := client.CreatePreAuthKeyWithResponse(ctx, request) + response, err := client.CreatePreAuthKey(ctx, request) if err != nil { return fmt.Errorf("creating preauthkey: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - preAuthKey := resp.JSON200.PreAuthKey - - return printOutput(cmd, preAuthKey, preAuthKey.Key) + return printOutput(cmd, response.GetPreAuthKey(), response.GetPreAuthKey().GetKey()) }), } -// preAuthKeyID reads the required --id flag for preauthkey commands. -func preAuthKeyID(cmd *cobra.Command) (uint64, error) { - id, _ := cmd.Flags().GetUint64("id") - if id == 0 { - return 0, fmt.Errorf("missing --id parameter: %w", errMissingParameter) - } - - return id, nil -} - var expirePreAuthKeyCmd = &cobra.Command{ - Use: cmdExpire, + Use: "expire", Short: "Expire a preauthkey", - Aliases: []string{"revoke", aliasExp, "e"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - id, err := preAuthKeyID(cmd) - if err != nil { - return err + Aliases: []string{"revoke", "exp", "e"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + id, _ := cmd.Flags().GetUint64("id") + + if id == 0 { + return fmt.Errorf("missing --id parameter: %w", errMissingParameter) } - idStr := strconv.FormatUint(id, util.Base10) + request := &v1.ExpirePreAuthKeyRequest{ + Id: id, + } - resp, err := client.ExpirePreAuthKeyWithResponse(ctx, clientv1.ExpirePreAuthKeyJSONRequestBody{ - Id: &idStr, - }) + response, err := client.ExpirePreAuthKey(ctx, request) if err != nil { return fmt.Errorf("expiring preauthkey: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Key expired") + return printOutput(cmd, response, "Key expired") }), } var deletePreAuthKeyCmd = &cobra.Command{ - Use: cmdDelete, + Use: "delete", Short: "Delete a preauthkey", - Aliases: []string{aliasDel, "rm", "d"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - id, err := preAuthKeyID(cmd) - if err != nil { - return err + Aliases: []string{"del", "rm", "d"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + id, _ := cmd.Flags().GetUint64("id") + + if id == 0 { + return fmt.Errorf("missing --id parameter: %w", errMissingParameter) } - idStr := strconv.FormatUint(id, util.Base10) + request := &v1.DeletePreAuthKeyRequest{ + Id: id, + } - resp, err := client.DeletePreAuthKeyWithResponse(ctx, &clientv1.DeletePreAuthKeyParams{ - Id: &idStr, - }) + response, err := client.DeletePreAuthKey(ctx, request) if err != nil { return fmt.Errorf("deleting preauthkey: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "Key deleted") + return printOutput(cmd, response, "Key deleted") }), } diff --git a/cmd/headscale/cli/root.go b/cmd/headscale/cli/root.go index ff06ea24b..fa7ded2dc 100644 --- a/cmd/headscale/cli/root.go +++ b/cmd/headscale/cli/root.go @@ -3,7 +3,6 @@ package cli import ( "os" "runtime" - "slices" "strings" "github.com/juanfont/headscale/hscontrol/types" @@ -31,7 +30,7 @@ func init() { Bool("force", false, "Disable prompts and forces the execution") // Re-enable usage output only for flag-parsing errors; runtime errors - // from [cobra.Command.RunE] should never dump usage text. + // from RunE should never dump usage text. rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { cmd.SilenceUsage = false @@ -96,9 +95,13 @@ func initConfig() { var prereleases = []string{"alpha", "beta", "rc", "dev"} func isPreReleaseVersion(version string) bool { - return slices.ContainsFunc(prereleases, func(unstable string) bool { - return strings.Contains(version, unstable) - }) + for _, unstable := range prereleases { + if strings.Contains(version, unstable) { + return true + } + } + + return false } // filterPreReleasesIfStable returns a function that filters out @@ -117,7 +120,13 @@ func filterPreReleasesIfStable(versionFunc func() string) func(string) bool { } // If we are on a stable release, filter out pre-releases. - return isPreReleaseVersion(tag) + for _, ignore := range prereleases { + if strings.Contains(tag, ignore) { + return true + } + } + + return false } } diff --git a/cmd/headscale/cli/root_test.go b/cmd/headscale/cli/root_test.go index fb1fefd11..8d1b9c01a 100644 --- a/cmd/headscale/cli/root_test.go +++ b/cmd/headscale/cli/root_test.go @@ -4,19 +4,6 @@ import ( "testing" ) -const ( - v23 = "0.23.0" - v23Alpha1 = "0.23.0-alpha.1" - v23Beta1 = "0.23.0-beta.1" - v23RC1 = "0.23.0-rc.1" - v23Dev = "0.23.0-dev" - v231 = "0.23.1" - - v24Alpha1Tag = "v0.24.0-alpha.1" - v24RCTag = "v0.24.0-rc.1" - v24Tag = "v0.24.0" -) - func TestFilterPreReleasesIfStable(t *testing.T) { tests := []struct { name string @@ -27,64 +14,64 @@ func TestFilterPreReleasesIfStable(t *testing.T) { }{ { name: "stable version filters alpha tag", - currentVersion: v23, - tag: v24Alpha1Tag, + currentVersion: "0.23.0", + tag: "v0.24.0-alpha.1", expectedFilter: true, description: "When on stable release, alpha tags should be filtered", }, { name: "stable version filters beta tag", - currentVersion: v23, + currentVersion: "0.23.0", tag: "v0.24.0-beta.2", expectedFilter: true, description: "When on stable release, beta tags should be filtered", }, { name: "stable version filters rc tag", - currentVersion: v23, - tag: v24RCTag, + currentVersion: "0.23.0", + tag: "v0.24.0-rc.1", expectedFilter: true, description: "When on stable release, rc tags should be filtered", }, { name: "stable version allows stable tag", - currentVersion: v23, - tag: v24Tag, + currentVersion: "0.23.0", + tag: "v0.24.0", expectedFilter: false, description: "When on stable release, stable tags should not be filtered", }, { name: "alpha version allows alpha tag", - currentVersion: v23Alpha1, + currentVersion: "0.23.0-alpha.1", tag: "v0.24.0-alpha.2", expectedFilter: false, description: "When on alpha release, alpha tags should not be filtered", }, { name: "alpha version allows beta tag", - currentVersion: v23Alpha1, + currentVersion: "0.23.0-alpha.1", tag: "v0.24.0-beta.1", expectedFilter: false, description: "When on alpha release, beta tags should not be filtered", }, { name: "alpha version allows rc tag", - currentVersion: v23Alpha1, - tag: v24RCTag, + currentVersion: "0.23.0-alpha.1", + tag: "v0.24.0-rc.1", expectedFilter: false, description: "When on alpha release, rc tags should not be filtered", }, { name: "alpha version allows stable tag", - currentVersion: v23Alpha1, - tag: v24Tag, + currentVersion: "0.23.0-alpha.1", + tag: "v0.24.0", expectedFilter: false, description: "When on alpha release, stable tags should not be filtered", }, { name: "beta version allows alpha tag", - currentVersion: v23Beta1, - tag: v24Alpha1Tag, + currentVersion: "0.23.0-beta.1", + tag: "v0.24.0-alpha.1", expectedFilter: false, description: "When on beta release, alpha tags should not be filtered", }, @@ -97,28 +84,28 @@ func TestFilterPreReleasesIfStable(t *testing.T) { }, { name: "beta version allows rc tag", - currentVersion: v23Beta1, - tag: v24RCTag, + currentVersion: "0.23.0-beta.1", + tag: "v0.24.0-rc.1", expectedFilter: false, description: "When on beta release, rc tags should not be filtered", }, { name: "beta version allows stable tag", - currentVersion: v23Beta1, - tag: v24Tag, + currentVersion: "0.23.0-beta.1", + tag: "v0.24.0", expectedFilter: false, description: "When on beta release, stable tags should not be filtered", }, { name: "rc version allows alpha tag", - currentVersion: v23RC1, - tag: v24Alpha1Tag, + currentVersion: "0.23.0-rc.1", + tag: "v0.24.0-alpha.1", expectedFilter: false, description: "When on rc release, alpha tags should not be filtered", }, { name: "rc version allows beta tag", - currentVersion: v23RC1, + currentVersion: "0.23.0-rc.1", tag: "v0.24.0-beta.1", expectedFilter: false, description: "When on rc release, beta tags should not be filtered", @@ -132,78 +119,78 @@ func TestFilterPreReleasesIfStable(t *testing.T) { }, { name: "rc version allows stable tag", - currentVersion: v23RC1, - tag: v24Tag, + currentVersion: "0.23.0-rc.1", + tag: "v0.24.0", expectedFilter: false, description: "When on rc release, stable tags should not be filtered", }, { name: "stable version with patch filters alpha", - currentVersion: v231, - tag: v24Alpha1Tag, + currentVersion: "0.23.1", + tag: "v0.24.0-alpha.1", expectedFilter: true, description: "Stable version with patch number should filter alpha tags", }, { name: "stable version with patch allows stable", - currentVersion: v231, - tag: v24Tag, + currentVersion: "0.23.1", + tag: "v0.24.0", expectedFilter: false, description: "Stable version with patch number should allow stable tags", }, { name: "tag with alpha substring in version number", - currentVersion: v23, + currentVersion: "0.23.0", tag: "v1.0.0-alpha.1", expectedFilter: true, description: "Tags with alpha in version string should be filtered on stable", }, { name: "tag with beta substring in version number", - currentVersion: v23, + currentVersion: "0.23.0", tag: "v1.0.0-beta.1", expectedFilter: true, description: "Tags with beta in version string should be filtered on stable", }, { name: "tag with rc substring in version number", - currentVersion: v23, + currentVersion: "0.23.0", tag: "v1.0.0-rc.1", expectedFilter: true, description: "Tags with rc in version string should be filtered on stable", }, { name: "empty tag on stable version", - currentVersion: v23, + currentVersion: "0.23.0", tag: "", expectedFilter: false, description: "Empty tags should not be filtered", }, { name: "dev version allows all tags", - currentVersion: v23Dev, - tag: v24Alpha1Tag, + currentVersion: "0.23.0-dev", + tag: "v0.24.0-alpha.1", expectedFilter: false, description: "Dev versions should not filter any tags (pre-release allows all)", }, { name: "stable version filters dev tag", - currentVersion: v23, + currentVersion: "0.23.0", tag: "v0.24.0-dev", expectedFilter: true, description: "When on stable release, dev tags should be filtered", }, { name: "dev version allows dev tag", - currentVersion: v23Dev, + currentVersion: "0.23.0-dev", tag: "v0.24.0-dev.1", expectedFilter: false, description: "When on dev release, dev tags should not be filtered", }, { name: "dev version allows stable tag", - currentVersion: v23Dev, - tag: v24Tag, + currentVersion: "0.23.0-dev", + tag: "v0.24.0", expectedFilter: false, description: "When on dev release, stable tags should not be filtered", }, @@ -213,8 +200,7 @@ func TestFilterPreReleasesIfStable(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result := filterPreReleasesIfStable(func() string { return tt.currentVersion })(tt.tag) if result != tt.expectedFilter { - t.Errorf( - "%s: got %v, want %v\nDescription: %s\nCurrent version: %s, Tag: %s", + t.Errorf("%s: got %v, want %v\nDescription: %s\nCurrent version: %s, Tag: %s", tt.name, result, tt.expectedFilter, @@ -236,25 +222,25 @@ func TestIsPreReleaseVersion(t *testing.T) { }{ { name: "stable version", - version: v23, + version: "0.23.0", expected: false, description: "Stable version should not be pre-release", }, { name: "alpha version", - version: v23Alpha1, + version: "0.23.0-alpha.1", expected: true, description: "Alpha version should be pre-release", }, { name: "beta version", - version: v23Beta1, + version: "0.23.0-beta.1", expected: true, description: "Beta version should be pre-release", }, { name: "rc version", - version: v23RC1, + version: "0.23.0-rc.1", expected: true, description: "RC version should be pre-release", }, @@ -272,7 +258,7 @@ func TestIsPreReleaseVersion(t *testing.T) { }, { name: "dev version", - version: v23Dev, + version: "0.23.0-dev", expected: true, description: "Dev version should be pre-release", }, @@ -284,7 +270,7 @@ func TestIsPreReleaseVersion(t *testing.T) { }, { name: "version with patch number", - version: v231, + version: "0.23.1", expected: false, description: "Stable version with patch should not be pre-release", }, @@ -294,8 +280,7 @@ func TestIsPreReleaseVersion(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result := isPreReleaseVersion(tt.version) if result != tt.expected { - t.Errorf( - "%s: got %v, want %v\nDescription: %s\nVersion: %s", + t.Errorf("%s: got %v, want %v\nDescription: %s\nVersion: %s", tt.name, result, tt.expected, diff --git a/cmd/headscale/cli/strings.go b/cmd/headscale/cli/strings.go deleted file mode 100644 index f6c003c63..000000000 --- a/cmd/headscale/cli/strings.go +++ /dev/null @@ -1,23 +0,0 @@ -package cli - -// Shared CLI vocabulary used across multiple command definitions in this -// package. Centralising the strings prevents goconst drift and ensures a -// typo in a subcommand name fails to compile rather than silently -// breaking the binding. -const ( - // Subcommand verbs (cobra Use field). - cmdList = "list" - cmdShow = "show" - cmdNew = "new" - cmdDelete = "delete" - cmdExpire = "expire" - - // Subcommand aliases. - aliasDel = "del" - aliasExp = "exp" - - // Output table column headers and printOutput map keys. - colResult = "Result" - colCreated = "Created" - colExpiration = "Expiration" -) diff --git a/cmd/headscale/cli/users.go b/cmd/headscale/cli/users.go index 71be49cf4..1e4609cec 100644 --- a/cmd/headscale/cli/users.go +++ b/cmd/headscale/cli/users.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" - "net/http" "net/url" "strconv" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util/zlog/zf" + "github.com/pterm/pterm" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -37,50 +37,13 @@ func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string, error) { // Normalise unset/negative identifiers to 0 so the uint64 // conversion does not produce a bogus large value. - identifier = max(identifier, 0) + if identifier < 0 { + identifier = 0 + } return uint64(identifier), username, nil //nolint:gosec // identifier is clamped to >= 0 above } -// resolveSingleUser resolves exactly one user from the --name/--id flags, -// returning the raw flag id and the matched user. -func resolveSingleUser( - ctx context.Context, - client *clientv1.ClientWithResponses, - cmd *cobra.Command, -) (uint64, *clientv1.User, error) { - id, username, err := usernameAndIDFromFlag(cmd) - if err != nil { - return 0, nil, err - } - - params := &clientv1.ListUsersParams{} - if username != "" { - params.Name = &username - } - - if id != 0 { - idStr := strconv.FormatUint(id, util.Base10) - params.Id = &idStr - } - - resp, err := client.ListUsersWithResponse(ctx, params) - if err != nil { - return 0, nil, fmt.Errorf("listing users: %w", err) - } - - if resp.StatusCode() != http.StatusOK { - return 0, nil, apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - users := resp.JSON200.Users - if len(users) != 1 { - return 0, nil, errMultipleUsersMatch - } - - return id, &users[0], nil -} - func init() { rootCmd.AddCommand(userCmd) userCmd.AddCommand(createUserCmd) @@ -107,7 +70,7 @@ var userCmd = &cobra.Command{ var createUserCmd = &cobra.Command{ Use: "create NAME", Short: "Creates a new user", - Aliases: []string{"c", cmdNew}, + Aliases: []string{"c", "new"}, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errMissingParameter @@ -115,19 +78,19 @@ var createUserCmd = &cobra.Command{ return nil }, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { userName := args[0] - log.Trace().Interface(zf.Client, client).Msg("obtained API client") + log.Trace().Interface(zf.Client, client).Msg("obtained gRPC client") - request := clientv1.CreateUserJSONRequestBody{Name: &userName} + request := &v1.CreateUserRequest{Name: userName} if displayName, _ := cmd.Flags().GetString("display-name"); displayName != "" { - request.DisplayName = &displayName + request.DisplayName = displayName } if email, _ := cmd.Flags().GetString("email"); email != "" { - request.Email = &email + request.Email = email } if pictureURL, _ := cmd.Flags().GetString("picture-url"); pictureURL != "" { @@ -135,60 +98,70 @@ var createUserCmd = &cobra.Command{ return fmt.Errorf("invalid picture URL: %w", err) } - request.PictureUrl = &pictureURL + request.PictureUrl = pictureURL } log.Trace().Interface(zf.Request, request).Msg("sending CreateUser request") - resp, err := client.CreateUserWithResponse(ctx, request) + response, err := client.CreateUser(ctx, request) if err != nil { return fmt.Errorf("creating user: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.User, "User created") + return printOutput(cmd, response.GetUser(), "User created") }), } var destroyUserCmd = &cobra.Command{ Use: "destroy --identifier ID or --name NAME", Short: "Destroys a user", - Aliases: []string{cmdDelete}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - _, user, err := resolveSingleUser(ctx, client, cmd) + Aliases: []string{"delete"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + id, username, err := usernameAndIDFromFlag(cmd) if err != nil { return err } - if !confirmAction(cmd, fmt.Sprintf( - "Do you want to remove the user %q (%s) and any associated preauthkeys?", - user.Name, user.Id, - )) { - return printOutput(cmd, map[string]string{colResult: "User not destroyed"}, "User not destroyed") + request := &v1.ListUsersRequest{ + Name: username, + Id: id, } - resp, err := client.DeleteUserWithResponse(ctx, user.Id) + users, err := client.ListUsers(ctx, request) + if err != nil { + return fmt.Errorf("listing users: %w", err) + } + + if len(users.GetUsers()) != 1 { + return errMultipleUsersMatch + } + + user := users.GetUsers()[0] + + if !confirmAction(cmd, fmt.Sprintf( + "Do you want to remove the user %q (%d) and any associated preauthkeys?", + user.GetName(), user.GetId(), + )) { + return printOutput(cmd, map[string]string{"Result": "User not destroyed"}, "User not destroyed") + } + + deleteRequest := &v1.DeleteUserRequest{Id: user.GetId()} + + response, err := client.DeleteUser(ctx, deleteRequest) if err != nil { return fmt.Errorf("destroying user: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200, "User destroyed") + return printOutput(cmd, response, "User destroyed") }), } var listUsersCmd = &cobra.Command{ - Use: cmdList, + Use: "list", Short: "List all the users", - Aliases: []string{"ls", cmdShow}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - params := &clientv1.ListUsersParams{} + Aliases: []string{"ls", "show"}, + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + request := &v1.ListUsersRequest{} id, _ := cmd.Flags().GetInt64("identifier") username, _ := cmd.Flags().GetString("name") @@ -197,41 +170,34 @@ var listUsersCmd = &cobra.Command{ // filter by one param at most switch { case id > 0: - idStr := strconv.FormatInt(id, util.Base10) - params.Id = &idStr + request.Id = uint64(id) case username != "": - params.Name = &username + request.Name = username case email != "": - params.Email = &email + request.Email = email } - resp, err := client.ListUsersWithResponse(ctx, params) + response, err := client.ListUsers(ctx, request) if err != nil { return fmt.Errorf("listing users: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - users := resp.JSON200.Users - - return printListOutput(cmd, users, func() error { - rows := make([][]string, 0, len(users)) - for _, user := range users { - rows = append( - rows, + return printListOutput(cmd, response.GetUsers(), func() error { + tableData := pterm.TableData{{"ID", "Name", "Username", "Email", "Created"}} + for _, user := range response.GetUsers() { + tableData = append( + tableData, []string{ - user.Id, - user.DisplayName, - user.Name, - user.Email, - user.CreatedAt.Format(HeadscaleDateTimeFormat), + strconv.FormatUint(user.GetId(), util.Base10), + user.GetDisplayName(), + user.GetName(), + user.GetEmail(), + user.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat), }, ) } - return renderTable([]string{"ID", "Name", "Username", "Email", colCreated}, rows) + return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() }) }), } @@ -240,23 +206,38 @@ var renameUserCmd = &cobra.Command{ Use: "rename", Short: "Renames a user", Aliases: []string{"mv"}, - RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error { - id, _, err := resolveSingleUser(ctx, client, cmd) + RunE: grpcRunE(func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error { + id, username, err := usernameAndIDFromFlag(cmd) if err != nil { return err } + listReq := &v1.ListUsersRequest{ + Name: username, + Id: id, + } + + users, err := client.ListUsers(ctx, listReq) + if err != nil { + return fmt.Errorf("listing users: %w", err) + } + + if len(users.GetUsers()) != 1 { + return errMultipleUsersMatch + } + newName, _ := cmd.Flags().GetString("new-name") - resp, err := client.RenameUserWithResponse(ctx, strconv.FormatUint(id, util.Base10), newName) + renameReq := &v1.RenameUserRequest{ + OldId: id, + NewName: newName, + } + + response, err := client.RenameUser(ctx, renameReq) if err != nil { return fmt.Errorf("renaming user: %w", err) } - if resp.StatusCode() != http.StatusOK { - return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault) - } - - return printOutput(cmd, resp.JSON200.User, "User renamed") + return printOutput(cmd, response.GetUser(), "User renamed") }), } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 9a5c311c2..f5bf47f8d 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -6,23 +6,21 @@ import ( "encoding/json" "errors" "fmt" - "net" - "net/http" "os" - "slices" - "strings" "time" - "github.com/cenkalti/backoff/v5" - clientv1 "github.com/juanfont/headscale/gen/client/v1" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util/zlog/zf" "github.com/prometheus/common/model" - "github.com/pterm/pterm" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/types/known/timestamppb" "gopkg.in/yaml.v3" ) @@ -38,45 +36,11 @@ const ( var ( errAPIKeyNotSet = errors.New("HEADSCALE_CLI_API_KEY environment variable needs to be set") errMissingParameter = errors.New("missing parameters") - errResponseStatus = errors.New("unexpected response status") ) -// apiError turns a non-2xx response into an error, surfacing the server's -// RFC7807 problem detail. detail holds the operation context and errors[] the -// wrapped cause (e.g. "name is too long"); both are joined so the server's -// message text is not lost. -func apiError(statusCode int, problem *clientv1.ErrorModel) error { - if problem == nil { - return fmt.Errorf("%w: %d %s", errResponseStatus, statusCode, http.StatusText(statusCode)) - } - - parts := make([]string, 0, 2) - - if problem.Detail != nil && *problem.Detail != "" { - parts = append(parts, *problem.Detail) - } - - if problem.Errors != nil { - for _, e := range *problem.Errors { - if e.Message != nil && *e.Message != "" { - parts = append(parts, *e.Message) - } - } - } - - if len(parts) == 0 && problem.Title != nil && *problem.Title != "" { - parts = append(parts, *problem.Title) - } - - if len(parts) == 0 { - return fmt.Errorf("%w: %d %s", errResponseStatus, statusCode, http.StatusText(statusCode)) - } - - return fmt.Errorf("%w: %s", errResponseStatus, strings.Join(parts, ": ")) -} - -// mustMarkRequired marks the named flags as required, panicking on an unknown -// flag. Only called from init(), where a failure is a programming error. +// mustMarkRequired marks the named flags as required on cmd, panicking +// if any name does not match a registered flag. This is only called +// from init() where a failure indicates a programming error. func mustMarkRequired(cmd *cobra.Command, names ...string) { for _, n := range names { err := cmd.MarkFlagRequired(n) @@ -103,47 +67,28 @@ func newHeadscaleServerWithConfig() (*hscontrol.Headscale, error) { return app, nil } -// clientRunE wraps a [cobra.Command.RunE] func, injecting a ready API client -// and a context whose timeout/cancel the wrapper owns. -func clientRunE( - fn func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error, +// grpcRunE wraps a cobra RunE func, injecting a ready gRPC client and +// context. Connection lifecycle is managed by the wrapper — callers +// never see the underlying conn or cancel func. +func grpcRunE( + fn func(ctx context.Context, client v1.HeadscaleServiceClient, cmd *cobra.Command, args []string) error, ) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - ctx, client, cancel, err := newHeadscaleCLIWithConfig() + ctx, client, conn, cancel, err := newHeadscaleCLIWithConfig() if err != nil { return fmt.Errorf("connecting to headscale: %w", err) } defer cancel() + defer conn.Close() return fn(ctx, client, cmd, args) } } -// withClient runs fn with an API client. For commands that branch on a flag -// before talking to the server, where clientRunE's whole-RunE wrapping does -// not fit. -func withClient( - fn func(ctx context.Context, client *clientv1.ClientWithResponses) error, -) error { - ctx, client, cancel, err := newHeadscaleCLIWithConfig() - if err != nil { - return fmt.Errorf("connecting to headscale: %w", err) - } - defer cancel() - - return fn(ctx, client) -} - -// newHeadscaleCLIWithConfig builds an HTTP client for the Headscale v1 API. -// -// When cfg.CLI.Address is unset the CLI is assumed to run on the server host -// and talks to the unix socket over HTTP without authentication (local trust). -// Otherwise it talks to the remote TCP address over HTTPS and injects the -// configured API key as a bearer token. -func newHeadscaleCLIWithConfig() (context.Context, *clientv1.ClientWithResponses, context.CancelFunc, error) { +func newHeadscaleCLIWithConfig() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc, error) { cfg, err := types.LoadCLIConfig() if err != nil { - return nil, nil, nil, fmt.Errorf("loading configuration: %w", err) + return nil, nil, nil, nil, fmt.Errorf("loading configuration: %w", err) } log.Debug(). @@ -152,131 +97,89 @@ func newHeadscaleCLIWithConfig() (context.Context, *clientv1.ClientWithResponses ctx, cancel := context.WithTimeout(context.Background(), cfg.CLI.Timeout) + grpcOptions := []grpc.DialOption{ + grpc.WithBlock(), //nolint:staticcheck // SA1019: deprecated but supported in 1.x + } + address := cfg.CLI.Address - // If the address is not set, we assume that we are on the server hosting [hscontrol]. + // If the address is not set, we assume that we are on the server hosting hscontrol. if address == "" { log.Debug(). Str("socket", cfg.UnixSocket). Msgf("HEADSCALE_CLI_ADDRESS environment is not set, connecting to unix socket.") - client, err := newSocketClient(cfg.UnixSocket) + address = cfg.UnixSocket + + // Try to give the user better feedback if we cannot write to the headscale + // socket. Note: os.OpenFile on a Unix domain socket returns ENXIO on + // Linux which is expected — only permission errors are actionable here. + // The actual gRPC connection uses net.Dial which handles sockets properly. + socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) //nolint if err != nil { + if os.IsPermission(err) { + cancel() + + return nil, nil, nil, nil, fmt.Errorf( + "unable to read/write to headscale socket %q, do you have the correct permissions? %w", + cfg.UnixSocket, + err, + ) + } + } else { + socket.Close() + } + + grpcOptions = append( + grpcOptions, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(util.GrpcSocketDialer), + ) + } else { + // If we are not connecting to a local server, require an API key for authentication + apiKey := cfg.CLI.APIKey + if apiKey == "" { cancel() - return nil, nil, nil, err + return nil, nil, nil, nil, errAPIKeyNotSet } - log.Trace().Caller().Str(zf.Address, cfg.UnixSocket).Msg("connecting via unix socket") + grpcOptions = append(grpcOptions, + grpc.WithPerRPCCredentials(tokenAuth{ + token: apiKey, + }), + ) - return ctx, client, cancel, nil - } + if cfg.CLI.Insecure { + tlsConfig := &tls.Config{ + // turn of gosec as we are intentionally setting + // insecure. + //nolint:gosec + InsecureSkipVerify: true, + } - // Remote connections require an API key for authentication. - apiKey := cfg.CLI.APIKey - if apiKey == "" { - cancel() - - return nil, nil, nil, errAPIKeyNotSet - } - - client, err := newRemoteClient(address, apiKey, cfg.CLI.Insecure) - if err != nil { - cancel() - - return nil, nil, nil, err - } - - log.Trace().Caller().Str(zf.Address, address).Msg("connecting via HTTPS") - - return ctx, client, cancel, nil -} - -// newSocketClient builds an API client that dials the local unix socket. The -// base-URL host is irrelevant; the custom dialer routes every request to the -// socket. -func newSocketClient(socketPath string) (*clientv1.ClientWithResponses, error) { - // Probe for a clearer permission error up front. [os.OpenFile] on a unix - // socket returns ENXIO on Linux (expected); only permission errors are - // actionable. The real connection goes through [net.Dial]. - socket, err := os.OpenFile(socketPath, os.O_WRONLY, SocketWritePermissions) //nolint - if err != nil { - if os.IsPermission(err) { - return nil, fmt.Errorf( - "unable to read/write to headscale socket %q, do you have the correct permissions? %w", - socketPath, - err, + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + ) + } else { + grpcOptions = append(grpcOptions, + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")), ) } - } else { - socket.Close() } - httpClient := &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialHeadscaleSocket(ctx, socketPath) - }, - }, + log.Trace().Caller().Str(zf.Address, address).Msg("connecting via gRPC") + + conn, err := grpc.DialContext(ctx, address, grpcOptions...) //nolint:staticcheck // SA1019: deprecated but supported in 1.x + if err != nil { + cancel() + + return nil, nil, nil, nil, fmt.Errorf("connecting to %s: %w", address, err) } - return clientv1.NewClientWithResponses( - "http://local", - clientv1.WithHTTPClient(httpClient), - ) -} + client := v1.NewHeadscaleServiceClient(conn) -// dialHeadscaleSocket connects to the unix socket, retrying until it appears or -// ctx (the CLI timeout) expires. The socket is created late in startup (after -// noise key, database, migrations), so a command run right after the server -// starts can race its creation; retrying preserves the old gRPC client's -// blocking-dial tolerance rather than failing on a not-yet-present socket. -func dialHeadscaleSocket(ctx context.Context, socketPath string) (net.Conn, error) { - b := backoff.NewExponentialBackOff() - b.InitialInterval = 50 * time.Millisecond - b.MaxInterval = 1 * time.Second - - return backoff.Retry(ctx, func() (net.Conn, error) { - return util.SocketDialer(ctx, socketPath) - }, backoff.WithBackOff(b)) -} - -// clientBaseURL turns a configured CLI address into a client base URL. A bare -// host[:port] (the historical form) defaults to https; an address that already -// carries a scheme is used as-is, so an explicit http:// or https:// is honoured -// rather than doubled into https://https://... -func clientBaseURL(address string) string { - if strings.Contains(address, "://") { - return address - } - - return "https://" + address -} - -// newRemoteClient builds an API client for a remote Headscale over HTTPS, -// honouring insecure (skip TLS verification) and injecting the API key as a -// bearer token on every request. -func newRemoteClient(address, apiKey string, insecure bool) (*clientv1.ClientWithResponses, error) { - transport := &http.Transport{} - if insecure { - transport.TLSClientConfig = &tls.Config{ - // turn off gosec as we are intentionally setting insecure. - //nolint:gosec - InsecureSkipVerify: true, - } - } - - httpClient := &http.Client{Transport: transport} - - return clientv1.NewClientWithResponses( - clientBaseURL(address), - clientv1.WithHTTPClient(httpClient), - clientv1.WithRequestEditorFn(func(_ context.Context, req *http.Request) error { - req.Header.Set("Authorization", "Bearer "+apiKey) - - return nil - }), - ) + return ctx, client, conn, cancel, nil } // formatOutput serialises result into the requested format. For the @@ -325,16 +228,16 @@ func printOutput(cmd *cobra.Command, result any, override string) error { } // expirationFromFlag parses the --expiration flag as a Prometheus-style -// duration (e.g. "90d", "1h") and returns an absolute time. -func expirationFromFlag(cmd *cobra.Command) (time.Time, error) { +// duration (e.g. "90d", "1h") and returns an absolute timestamp. +func expirationFromFlag(cmd *cobra.Command) (*timestamppb.Timestamp, error) { durationStr, _ := cmd.Flags().GetString("expiration") duration, err := model.ParseDuration(durationStr) if err != nil { - return time.Time{}, fmt.Errorf("parsing duration: %w", err) + return nil, fmt.Errorf("parsing duration: %w", err) } - return time.Now().UTC().Add(time.Duration(duration)), nil + return timestamppb.New(time.Now().UTC().Add(time.Duration(duration))), nil } // confirmAction returns true when the user confirms a prompt, or when @@ -348,19 +251,9 @@ func confirmAction(cmd *cobra.Command, prompt string) bool { return util.YesNo(prompt) } -// renderTable prints a human-readable pterm table with the given header row -// and data rows, using the shared header styling. -func renderTable(header []string, rows [][]string) error { - tableData := make(pterm.TableData, 0, 1+len(rows)) - tableData = append(tableData, header) - tableData = append(tableData, rows...) - - return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() -} - // printListOutput checks the --output flag: when a machine-readable format is -// requested it serialises data as JSON/YAML; otherwise it calls the render -// callback to produce the human-readable pterm table. +// requested it serialises data as JSON/YAML; otherwise it calls renderTable +// to produce the human-readable pterm table. func printListOutput( cmd *cobra.Command, data any, @@ -376,25 +269,56 @@ func printListOutput( // printError writes err to stderr, formatting it as JSON/YAML when the // --output flag requests machine-readable output. Used exclusively by -// [Execute] so that every error surfaces in the format the caller asked for. +// Execute() so that every error surfaces in the format the caller asked for. func printError(err error, outputFormat string) { type errOutput struct { Error string `json:"error"` } - if outputFormat == "" { + e := errOutput{Error: err.Error()} + + var formatted []byte + + switch outputFormat { + case outputFormatJSON: + formatted, _ = json.MarshalIndent(e, "", "\t") //nolint:errchkjson // errOutput contains only a string field + case outputFormatJSONLine: + formatted, _ = json.Marshal(e) //nolint:errchkjson // errOutput contains only a string field + case outputFormatYAML: + formatted, _ = yaml.Marshal(e) + default: fmt.Fprintf(os.Stderr, "Error: %s\n", err) return } - // formatOutput cannot fail here: errOutput is a single string field. - out, _ := formatOutput(errOutput{Error: err.Error()}, "", outputFormat) - fmt.Fprintf(os.Stderr, "%s\n", out) + fmt.Fprintf(os.Stderr, "%s\n", formatted) } func hasMachineOutputFlag() bool { - return slices.ContainsFunc(os.Args, func(arg string) bool { - return arg == outputFormatJSON || arg == outputFormatJSONLine || arg == outputFormatYAML - }) + for _, arg := range os.Args { + if arg == outputFormatJSON || arg == outputFormatJSONLine || arg == outputFormatYAML { + return true + } + } + + return false +} + +type tokenAuth struct { + token string +} + +// Return value is mapped to request headers. +func (t tokenAuth) GetRequestMetadata( + ctx context.Context, + in ...string, +) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + t.token, + }, nil +} + +func (tokenAuth) RequireTransportSecurity() bool { + return true } diff --git a/cmd/headscale/cli/utils_socket_test.go b/cmd/headscale/cli/utils_socket_test.go deleted file mode 100644 index cc9b22ed6..000000000 --- a/cmd/headscale/cli/utils_socket_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package cli - -import ( - "context" - "net" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestDialHeadscaleSocketRetriesUntilPresent proves the CLI socket dialer -// tolerates a not-yet-created socket (the server-still-starting race) by -// retrying until it appears, rather than failing immediately like a bare dial. -func TestDialHeadscaleSocketRetriesUntilPresent(t *testing.T) { - sock := filepath.Join(t.TempDir(), "headscale.sock") - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - type result struct { - conn net.Conn - err error - } - - done := make(chan result, 1) - - go func() { - conn, err := dialHeadscaleSocket(ctx, sock) - done <- result{conn, err} - }() - - // Listen only after the dialer has begun, so its backoff must retry the - // absent socket and connect once it exists. - var lc net.ListenConfig - - ln, err := lc.Listen(ctx, "unix", sock) - require.NoError(t, err) - - defer ln.Close() - - go func() { - if conn, _ := ln.Accept(); conn != nil { - conn.Close() - } - }() - - res := <-done - require.NoError(t, res.err) - require.NotNil(t, res.conn) - - res.conn.Close() -} - -// TestDialHeadscaleSocketRespectsDeadline proves the retry is bounded by the -// context: when the socket never appears, the dialer returns an error around the -// deadline instead of hanging. -func TestDialHeadscaleSocketRespectsDeadline(t *testing.T) { - sock := filepath.Join(t.TempDir(), "absent.sock") - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - start := time.Now() - - conn, err := dialHeadscaleSocket(ctx, sock) - require.Error(t, err) - assert.Nil(t, conn) - assert.Less(t, time.Since(start), 5*time.Second, "should stop near the deadline, not hang") -} diff --git a/cmd/headscale/cli/utils_test.go b/cmd/headscale/cli/utils_test.go deleted file mode 100644 index 845d676c5..000000000 --- a/cmd/headscale/cli/utils_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package cli - -import "testing" - -func TestClientBaseURL(t *testing.T) { - tests := []struct { - name string - address string - want string - }{ - { - name: "bare host defaults to https", - address: "headscale.example.com:50443", - want: "https://headscale.example.com:50443", - }, - { - name: "explicit https scheme is kept", - address: "https://headscale.example.com", - want: "https://headscale.example.com", - }, - { - name: "explicit http scheme is kept", - address: "http://localhost:8080", - want: "http://localhost:8080", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := clientBaseURL(tt.address); got != tt.want { - t.Errorf("clientBaseURL(%q) = %q, want %q", tt.address, got, tt.want) - } - }) - } -} diff --git a/cmd/headscale/headscale.go b/cmd/headscale/headscale.go index 933e78e06..679f082ee 100644 --- a/cmd/headscale/headscale.go +++ b/cmd/headscale/headscale.go @@ -11,7 +11,21 @@ import ( ) func main() { - colors := termcolor.SupportLevel(os.Stderr) != termcolor.LevelNone + var colors bool + + switch l := termcolor.SupportLevel(os.Stderr); l { + case termcolor.Level16M: + colors = true + case termcolor.Level256: + colors = true + case termcolor.LevelBasic: + colors = true + case termcolor.LevelNone: + colors = false + default: + // no color, return text as is. + colors = false + } // Adhere to no-color.org manifesto of allowing users to // turn off color in cli/services diff --git a/cmd/hi/cleanup.go b/cmd/hi/cleanup.go index d58ab779e..26db49ae9 100644 --- a/cmd/hi/cleanup.go +++ b/cmd/hi/cleanup.go @@ -71,8 +71,26 @@ func killTestContainers(ctx context.Context) error { removed := 0 for _, cont := range containers { - if isTestContainerName(cont.Names) { - if killAndRemove(ctx, cli, cont) { + shouldRemove := false + + for _, name := range cont.Names { + if strings.Contains(name, "headscale-test-suite") || + strings.Contains(name, "hs-") || + strings.Contains(name, "ts-") || + strings.Contains(name, "derp-") { + shouldRemove = true + break + } + } + + if shouldRemove { + // First kill the container if it's running + if cont.State == "running" { + _ = cli.ContainerKill(ctx, cont.ID, "KILL") + } + + // Then remove the container with retry logic + if removeContainerWithRetry(ctx, cli, cont.ID) { removed++ } } @@ -111,7 +129,13 @@ func killTestContainersByRunID(ctx context.Context, runID string) error { removed := 0 for _, cont := range containers { - if killAndRemove(ctx, cli, cont) { + // Kill the container if it's running + if cont.State == "running" { + _ = cli.ContainerKill(ctx, cont.ID, "KILL") + } + + // Remove the container with retry logic + if removeContainerWithRetry(ctx, cli, cont.ID) { removed++ } } @@ -149,8 +173,20 @@ func cleanupStaleTestContainers(ctx context.Context) error { for _, cont := range containers { // Only remove containers that look like test containers - if isTestContainerName(cont.Names) { - if killAndRemove(ctx, cli, cont) { + shouldRemove := false + + for _, name := range cont.Names { + if strings.Contains(name, "headscale-test-suite") || + strings.Contains(name, "hs-") || + strings.Contains(name, "ts-") || + strings.Contains(name, "derp-") { + shouldRemove = true + break + } + } + + if shouldRemove { + if removeContainerWithRetry(ctx, cli, cont.ID) { removed++ } } @@ -187,46 +223,6 @@ func removeContainerWithRetry(ctx context.Context, cli *client.Client, container return err == nil } -// testContainerNamePrefixes are the name prefixes used by containers that the -// integration test harness creates (headscale, tailscale, DERP, and k3s). -var testContainerNamePrefixes = []string{"hs-", "ts-", "derp-", "k3s-"} - -// matchesTestContainerPrefix reports whether name belongs to an integration -// test container, ignoring any leading "/" that Docker prefixes names with. -func matchesTestContainerPrefix(name string) bool { - name = strings.TrimPrefix(name, "/") - for _, prefix := range testContainerNamePrefixes { - if strings.HasPrefix(name, prefix) { - return true - } - } - - return false -} - -// isTestContainerName reports whether any of the container names belong to an -// integration test container. -func isTestContainerName(names []string) bool { - for _, name := range names { - if strings.Contains(name, "headscale-test-suite") || - matchesTestContainerPrefix(name) { - return true - } - } - - return false -} - -// killAndRemove kills a running container then removes it with retry logic, -// reporting whether the removal succeeded. -func killAndRemove(ctx context.Context, cli *client.Client, cont container.Summary) bool { - if cont.State == "running" { - _ = cli.ContainerKill(ctx, cont.ID, "KILL") - } - - return removeContainerWithRetry(ctx, cli, cont.ID) -} - // pruneDockerNetworks removes unused Docker networks. func pruneDockerNetworks(ctx context.Context) error { cli, err := createDockerClient(ctx) diff --git a/cmd/hi/docker.go b/cmd/hi/docker.go index 102ee526e..060057a9c 100644 --- a/cmd/hi/docker.go +++ b/cmd/hi/docker.go @@ -14,7 +14,6 @@ import ( "strings" "time" - "github.com/cenkalti/backoff/v5" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/mount" @@ -523,9 +522,9 @@ func checkImageAvailableLocally(ctx context.Context, cli *client.Client, imageNa return true, nil } -// ensureImageAvailable pulls imageName if missing, using Docker Hub -// credentials and retrying transient errors. +// ensureImageAvailable checks if the image is available locally first, then pulls if needed. func ensureImageAvailable(ctx context.Context, cli *client.Client, imageName string, verbose bool) error { + // First check if image is available locally available, err := checkImageAvailableLocally(ctx, cli, imageName) if err != nil { return fmt.Errorf("checking local image availability: %w", err) @@ -539,64 +538,34 @@ func ensureImageAvailable(ctx context.Context, cli *client.Client, imageName str return nil } + // Image not available locally, try to pull it if verbose { log.Printf("Image %s not found locally, pulling...", imageName) } - registryAuth, err := dockertestutil.RegistryAuth() + reader, err := cli.ImagePull(ctx, imageName, image.PullOptions{}) if err != nil { - return fmt.Errorf("resolving registry auth: %w", err) + return fmt.Errorf("pulling image %s: %w", imageName, err) } + defer reader.Close() - _, err = backoff.Retry( - ctx, - func() (struct{}, error) { - reader, pullErr := cli.ImagePull(ctx, imageName, image.PullOptions{RegistryAuth: registryAuth}) - if pullErr != nil { - if isPermanentDockerPullError(pullErr) { - return struct{}{}, backoff.Permanent(pullErr) - } + if verbose { + _, err = io.Copy(os.Stdout, reader) + if err != nil { + return fmt.Errorf("reading pull output: %w", err) + } + } else { + _, err = io.Copy(io.Discard, reader) + if err != nil { + return fmt.Errorf("reading pull output: %w", err) + } - return struct{}{}, fmt.Errorf("pulling image %s: %w", imageName, pullErr) - } - defer reader.Close() - - sink := io.Discard - if verbose { - sink = os.Stdout - } - - _, copyErr := io.Copy(sink, reader) - if copyErr != nil { - return struct{}{}, fmt.Errorf("reading pull output: %w", copyErr) - } - - return struct{}{}, nil - }, - backoff.WithBackOff(backoff.NewExponentialBackOff()), - backoff.WithMaxElapsedTime(60*time.Second), - ) - if err != nil { - return err - } - - if !verbose { log.Printf("Image %s pulled successfully", imageName) } return nil } -func isPermanentDockerPullError(err error) bool { - msg := strings.ToLower(err.Error()) - - return strings.Contains(msg, "manifest unknown") || - strings.Contains(msg, "manifest not found") || - strings.Contains(msg, "repository does not exist") || - strings.Contains(msg, "name unknown") || - strings.Contains(msg, "no such image") -} - // listControlFiles displays the headscale test artifacts created in the control logs directory. func listControlFiles(logsDir string) { entries, err := os.ReadDir(logsDir) @@ -735,7 +704,7 @@ func getCurrentTestContainers(containers []container.Summary, testContainerID st for _, cont := range containers { for _, name := range cont.Names { containerName := strings.TrimPrefix(name, "/") - if matchesTestContainerPrefix(containerName) { + if strings.HasPrefix(containerName, "hs-") || strings.HasPrefix(containerName, "ts-") { // Check if container has matching run ID label if cont.Labels != nil && cont.Labels["hi.run-id"] == runID { testRunContainers = append(testRunContainers, testContainer{ @@ -769,6 +738,17 @@ func extractContainerArtifacts(ctx context.Context, cli *client.Client, containe return fmt.Errorf("extracting logs: %w", err) } + // Extract tar files for headscale containers only + if strings.HasPrefix(containerName, "hs-") { + err := extractContainerFiles(ctx, cli, containerID, containerName, logsDir, verbose) + if err != nil { + if verbose { + log.Printf("Warning: failed to extract files from %s: %v", containerName, err) + } + // Don't fail the whole extraction if files are missing + } + } + return nil } @@ -816,3 +796,12 @@ func extractContainerLogs(ctx context.Context, cli *client.Client, containerID, return nil } + +// extractContainerFiles extracts database file and directories from headscale containers. +// Note: The actual file extraction is now handled by the integration tests themselves +// via SaveProfile, SaveMapResponses, and SaveDatabase functions in hsic.go. +func extractContainerFiles(ctx context.Context, cli *client.Client, containerID, containerName, logsDir string, verbose bool) error { + // Files are now extracted directly by the integration tests + // This function is kept for potential future use or other file types + return nil +} diff --git a/cmd/hi/doctor.go b/cmd/hi/doctor.go index 7bd42fb90..1791d66de 100644 --- a/cmd/hi/doctor.go +++ b/cmd/hi/doctor.go @@ -5,25 +5,8 @@ import ( "errors" "fmt" "log" - "os" "os/exec" "strings" - - "github.com/juanfont/headscale/integration/dockertestutil" - "github.com/juanfont/headscale/integration/k3sic" -) - -const ( - statusPass = "PASS" - statusFail = "FAIL" - statusWarn = "WARN" - - nameDockerDaemon = "Docker Daemon" - nameDockerContext = "Docker Context" - nameDockerSocket = "Docker Socket" - nameGolangImage = "Golang Image" - nameK3sImage = "K3s Image" - nameGoInstall = "Go Installation" ) var ErrSystemChecksFailed = errors.New("system checks failed") @@ -36,21 +19,6 @@ type DoctorResult struct { Suggestions []string } -// pass builds a passing DoctorResult. -func pass(name, message string) DoctorResult { - return DoctorResult{Name: name, Status: statusPass, Message: message} -} - -// warn builds a warning DoctorResult with optional suggestions. -func warn(name, message string, suggestions ...string) DoctorResult { - return DoctorResult{Name: name, Status: statusWarn, Message: message, Suggestions: suggestions} -} - -// fail builds a failing DoctorResult with optional suggestions. -func fail(name, message string, suggestions ...string) DoctorResult { - return DoctorResult{Name: name, Status: statusFail, Message: message, Suggestions: suggestions} -} - // runDoctorCheck performs comprehensive pre-flight checks for integration testing. func runDoctorCheck(ctx context.Context) error { results := []DoctorResult{} @@ -63,12 +31,10 @@ func runDoctorCheck(ctx context.Context) error { results = append(results, dockerResult) // If Docker is available, run additional checks - if dockerResult.Status == statusPass { + if dockerResult.Status == "PASS" { results = append(results, checkDockerContext(ctx)) results = append(results, checkDockerSocket(ctx)) - results = append(results, checkDockerHubCredentials()) results = append(results, checkGolangImage(ctx)) - results = append(results, checkK3sImage(ctx)) } // Check 3: Go installation @@ -85,7 +51,7 @@ func runDoctorCheck(ctx context.Context) error { // Return error if any critical checks failed for _, result := range results { - if result.Status == statusFail { + if result.Status == "FAIL" { return fmt.Errorf("%w - see details above", ErrSystemChecksFailed) } } @@ -99,115 +65,140 @@ func runDoctorCheck(ctx context.Context) error { func checkDockerBinary() DoctorResult { _, err := exec.LookPath("docker") if err != nil { - return fail( - "Docker Binary", - "Docker binary not found in PATH", - "Install Docker: https://docs.docker.com/get-docker/", - "For macOS: consider using colima or Docker Desktop", - "Ensure docker is in your PATH", - ) + return DoctorResult{ + Name: "Docker Binary", + Status: "FAIL", + Message: "Docker binary not found in PATH", + Suggestions: []string{ + "Install Docker: https://docs.docker.com/get-docker/", + "For macOS: consider using colima or Docker Desktop", + "Ensure docker is in your PATH", + }, + } } - return pass("Docker Binary", "Docker binary found") + return DoctorResult{ + Name: "Docker Binary", + Status: "PASS", + Message: "Docker binary found", + } } // checkDockerDaemon verifies Docker daemon is running and accessible. func checkDockerDaemon(ctx context.Context) DoctorResult { cli, err := createDockerClient(ctx) if err != nil { - return fail( - nameDockerDaemon, - fmt.Sprintf("Cannot create Docker client: %v", err), - "Start Docker daemon/service", - "Check Docker Desktop is running (if using Docker Desktop)", - "For colima: run 'colima start'", - "Verify DOCKER_HOST environment variable if set", - ) + return DoctorResult{ + Name: "Docker Daemon", + Status: "FAIL", + Message: fmt.Sprintf("Cannot create Docker client: %v", err), + Suggestions: []string{ + "Start Docker daemon/service", + "Check Docker Desktop is running (if using Docker Desktop)", + "For colima: run 'colima start'", + "Verify DOCKER_HOST environment variable if set", + }, + } } defer cli.Close() _, err = cli.Ping(ctx) if err != nil { - return fail( - nameDockerDaemon, - fmt.Sprintf("Cannot ping Docker daemon: %v", err), - "Ensure Docker daemon is running", - "Check Docker socket permissions", - "Try: docker info", - ) + return DoctorResult{ + Name: "Docker Daemon", + Status: "FAIL", + Message: fmt.Sprintf("Cannot ping Docker daemon: %v", err), + Suggestions: []string{ + "Ensure Docker daemon is running", + "Check Docker socket permissions", + "Try: docker info", + }, + } } - return pass(nameDockerDaemon, "Docker daemon is running and accessible") + return DoctorResult{ + Name: "Docker Daemon", + Status: "PASS", + Message: "Docker daemon is running and accessible", + } } // checkDockerContext verifies Docker context configuration. func checkDockerContext(ctx context.Context) DoctorResult { contextInfo, err := getCurrentDockerContext(ctx) if err != nil { - return warn( - nameDockerContext, - "Could not detect Docker context, using default settings", - "Check: docker context ls", - "Consider setting up a specific context if needed", - ) + return DoctorResult{ + Name: "Docker Context", + Status: "WARN", + Message: "Could not detect Docker context, using default settings", + Suggestions: []string{ + "Check: docker context ls", + "Consider setting up a specific context if needed", + }, + } } if contextInfo == nil { - return pass(nameDockerContext, "Using default Docker context") + return DoctorResult{ + Name: "Docker Context", + Status: "PASS", + Message: "Using default Docker context", + } } - return pass(nameDockerContext, "Using Docker context: "+contextInfo.Name) + return DoctorResult{ + Name: "Docker Context", + Status: "PASS", + Message: "Using Docker context: " + contextInfo.Name, + } } // checkDockerSocket verifies Docker socket accessibility. func checkDockerSocket(ctx context.Context) DoctorResult { cli, err := createDockerClient(ctx) if err != nil { - return fail( - nameDockerSocket, - fmt.Sprintf("Cannot access Docker socket: %v", err), - "Check Docker socket permissions", - "Add user to docker group: sudo usermod -aG docker $USER", - "For colima: ensure socket is accessible", - ) + return DoctorResult{ + Name: "Docker Socket", + Status: "FAIL", + Message: fmt.Sprintf("Cannot access Docker socket: %v", err), + Suggestions: []string{ + "Check Docker socket permissions", + "Add user to docker group: sudo usermod -aG docker $USER", + "For colima: ensure socket is accessible", + }, + } } defer cli.Close() info, err := cli.Info(ctx) if err != nil { - return fail( - nameDockerSocket, - fmt.Sprintf("Cannot get Docker info: %v", err), - "Check Docker daemon status", - "Verify socket permissions", - ) + return DoctorResult{ + Name: "Docker Socket", + Status: "FAIL", + Message: fmt.Sprintf("Cannot get Docker info: %v", err), + Suggestions: []string{ + "Check Docker daemon status", + "Verify socket permissions", + }, + } } - return pass(nameDockerSocket, fmt.Sprintf("Docker socket accessible (Server: %s)", info.ServerVersion)) -} - -// checkDockerHubCredentials warns when pulls would be anonymous and -// therefore rate-limited. -func checkDockerHubCredentials() DoctorResult { - _, _, source := dockertestutil.Credentials() - if source == dockertestutil.CredentialSourceAnonymous { - return warn( - "Docker Hub Credentials", - "No Docker Hub credentials found — pulls will be rate-limited (100/6h per IP)", - "Run: docker login", - "Or export DOCKERHUB_USERNAME and DOCKERHUB_TOKEN", - "In CI: ensure the docker/login-action step is configured with secrets", - ) + return DoctorResult{ + Name: "Docker Socket", + Status: "PASS", + Message: fmt.Sprintf("Docker socket accessible (Server: %s)", info.ServerVersion), } - - return pass("Docker Hub Credentials", fmt.Sprintf("Credentials available (source: %s)", source)) } // checkGolangImage verifies the golang Docker image is available locally or can be pulled. func checkGolangImage(ctx context.Context) DoctorResult { cli, err := createDockerClient(ctx) if err != nil { - return fail(nameGolangImage, "Cannot create Docker client for image check") + return DoctorResult{ + Name: "Golang Image", + Status: "FAIL", + Message: "Cannot create Docker client for image check", + } } defer cli.Close() @@ -217,94 +208,81 @@ func checkGolangImage(ctx context.Context) DoctorResult { // First check if image is available locally available, err := checkImageAvailableLocally(ctx, cli, imageName) if err != nil { - return fail( - nameGolangImage, - fmt.Sprintf("Cannot check golang image %s: %v", imageName, err), - "Check Docker daemon status", - "Try: docker images | grep golang", - ) + return DoctorResult{ + Name: "Golang Image", + Status: "FAIL", + Message: fmt.Sprintf("Cannot check golang image %s: %v", imageName, err), + Suggestions: []string{ + "Check Docker daemon status", + "Try: docker images | grep golang", + }, + } } if available { - return pass(nameGolangImage, fmt.Sprintf("Golang image %s is available locally", imageName)) + return DoctorResult{ + Name: "Golang Image", + Status: "PASS", + Message: fmt.Sprintf("Golang image %s is available locally", imageName), + } } // Image not available locally, try to pull it err = ensureImageAvailable(ctx, cli, imageName, false) if err != nil { - return fail( - nameGolangImage, - fmt.Sprintf("Golang image %s not available locally and cannot pull: %v", imageName, err), - "Check internet connectivity", - "Verify Docker Hub access", - "Try: docker pull "+imageName, - "Or run tests offline if image was pulled previously", - ) + return DoctorResult{ + Name: "Golang Image", + Status: "FAIL", + Message: fmt.Sprintf("Golang image %s not available locally and cannot pull: %v", imageName, err), + Suggestions: []string{ + "Check internet connectivity", + "Verify Docker Hub access", + "Try: docker pull " + imageName, + "Or run tests offline if image was pulled previously", + }, + } } - return pass(nameGolangImage, fmt.Sprintf("Golang image %s is now available", imageName)) -} - -// checkK3sImage verifies the ghcr k3s image used by TestK8sOperator is available -// locally or can be pulled. The image is pinned (see [k3sic.K3sImage]). -func checkK3sImage(ctx context.Context) DoctorResult { - cli, err := createDockerClient(ctx) - if err != nil { - return fail(nameK3sImage, "Cannot create Docker client for image check") + return DoctorResult{ + Name: "Golang Image", + Status: "PASS", + Message: fmt.Sprintf("Golang image %s is now available", imageName), } - defer cli.Close() - - imageName := k3sic.K3sImage - - available, err := checkImageAvailableLocally(ctx, cli, imageName) - if err != nil { - return fail( - nameK3sImage, - fmt.Sprintf("Cannot check k3s image %s: %v", imageName, err), - "Check Docker daemon status", - "Try: docker images | grep k3s", - ) - } - - if available { - return pass(nameK3sImage, fmt.Sprintf("K3s image %s is available locally", imageName)) - } - - err = ensureImageAvailable(ctx, cli, imageName, false) - if err != nil { - return warn( - nameK3sImage, - fmt.Sprintf("K3s image %s not available locally and could not pull: %v", imageName, err), - "Only TestK8sOperator needs this image; other tests are unaffected", - "Try: docker pull "+imageName, - ) - } - - return pass(nameK3sImage, fmt.Sprintf("K3s image %s is now available", imageName)) } // checkGoInstallation verifies Go is installed and working. func checkGoInstallation(ctx context.Context) DoctorResult { _, err := exec.LookPath("go") if err != nil { - return fail( - nameGoInstall, - "Go binary not found in PATH", - "Install Go: https://golang.org/dl/", - "Ensure go is in your PATH", - ) + return DoctorResult{ + Name: "Go Installation", + Status: "FAIL", + Message: "Go binary not found in PATH", + Suggestions: []string{ + "Install Go: https://golang.org/dl/", + "Ensure go is in your PATH", + }, + } } cmd := exec.CommandContext(ctx, "go", "version") output, err := cmd.Output() if err != nil { - return fail(nameGoInstall, fmt.Sprintf("Cannot get Go version: %v", err)) + return DoctorResult{ + Name: "Go Installation", + Status: "FAIL", + Message: fmt.Sprintf("Cannot get Go version: %v", err), + } } version := strings.TrimSpace(string(output)) - return pass(nameGoInstall, version) + return DoctorResult{ + Name: "Go Installation", + Status: "PASS", + Message: version, + } } // checkGitRepository verifies we're in a git repository. @@ -313,19 +291,26 @@ func checkGitRepository(ctx context.Context) DoctorResult { err := cmd.Run() if err != nil { - return fail( - "Git Repository", - "Not in a Git repository", - "Run from within the headscale git repository", - "Clone the repository: git clone https://github.com/juanfont/headscale.git", - ) + return DoctorResult{ + Name: "Git Repository", + Status: "FAIL", + Message: "Not in a Git repository", + Suggestions: []string{ + "Run from within the headscale git repository", + "Clone the repository: git clone https://github.com/juanfont/headscale.git", + }, + } } - return pass("Git Repository", "Running in Git repository") + return DoctorResult{ + Name: "Git Repository", + Status: "PASS", + Message: "Running in Git repository", + } } // checkRequiredFiles verifies required files exist. -func checkRequiredFiles(_ context.Context) DoctorResult { +func checkRequiredFiles(ctx context.Context) DoctorResult { requiredFiles := []string{ "go.mod", "integration/", @@ -335,23 +320,32 @@ func checkRequiredFiles(_ context.Context) DoctorResult { var missingFiles []string for _, file := range requiredFiles { - _, err := os.Stat(file) + cmd := exec.CommandContext(ctx, "test", "-e", file) + + err := cmd.Run() if err != nil { missingFiles = append(missingFiles, file) } } if len(missingFiles) > 0 { - return fail( - "Required Files", - "Missing required files: "+strings.Join(missingFiles, ", "), - "Ensure you're in the headscale project root directory", - "Check that integration/ directory exists", - "Verify this is a complete headscale repository", - ) + return DoctorResult{ + Name: "Required Files", + Status: "FAIL", + Message: "Missing required files: " + strings.Join(missingFiles, ", "), + Suggestions: []string{ + "Ensure you're in the headscale project root directory", + "Check that integration/ directory exists", + "Verify this is a complete headscale repository", + }, + } } - return pass("Required Files", "All required files found") + return DoctorResult{ + Name: "Required Files", + Status: "PASS", + Message: "All required files found", + } } // displayDoctorResults shows the results in a formatted way. @@ -363,11 +357,11 @@ func displayDoctorResults(results []DoctorResult) { var icon string switch result.Status { - case statusPass: + case "PASS": icon = "✅" - case statusWarn: + case "WARN": icon = "⚠️" - case statusFail: + case "FAIL": icon = "❌" default: icon = "❓" diff --git a/cmd/hi/listversions.go b/cmd/hi/listversions.go deleted file mode 100644 index c2162ad64..000000000 --- a/cmd/hi/listversions.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/creachadair/command" - "github.com/juanfont/headscale/hscontrol/capver" -) - -var ( - errUnknownSet = errors.New("unknown --set value (want must|all)") - errUnknownFormat = errors.New("unknown --format value (want space|newline|json)") -) - -// ListVersionsConfig holds flags for the list-versions subcommand. -type ListVersionsConfig struct { - Set string `flag:"set,default=must,Version set: must|all"` - Exclude string `flag:"exclude,Comma-separated versions to exclude (e.g. head,unstable)"` - Format string `flag:"format,default=space,Output format: space|newline|json"` -} - -var listVersionsConfig ListVersionsConfig - -// listVersions prints the Tailscale versions used by integration tests -// in a format CI can shell out to. Mirrors integration/scenario.go -// AllVersions and MustTestVersions: "head" and "unstable" are bare -// tags, releases get a "v" prefix so each entry can be appended to -// "ghcr.io/tailscale/tailscale:" directly. -func listVersions(env *command.Env) error { - release := capver.TailscaleLatestMajorMinor(capver.SupportedMajorMinorVersions, true) - all := append([]string{"head", "unstable"}, release...) - must := append(append([]string{}, all[0:4]...), all[len(all)-2:]...) - - var versions []string - - switch listVersionsConfig.Set { - case "must": - versions = must - case "all": - versions = all - default: - return fmt.Errorf("%w: %q", errUnknownSet, listVersionsConfig.Set) - } - - excluded := make(map[string]bool) - - if listVersionsConfig.Exclude != "" { - for v := range strings.SplitSeq(listVersionsConfig.Exclude, ",") { - excluded[strings.TrimSpace(v)] = true - } - } - - out := make([]string, 0, len(versions)) - - for _, v := range versions { - if excluded[v] { - continue - } - - if v != "head" && v != "unstable" { - v = "v" + v - } - - out = append(out, v) - } - - switch listVersionsConfig.Format { - case "space": - fmt.Println(strings.Join(out, " ")) - case "newline": - for _, v := range out { - fmt.Println(v) - } - case "json": - b, err := json.Marshal(out) - if err != nil { - return err - } - - fmt.Println(string(b)) - default: - return fmt.Errorf("%w: %q", errUnknownFormat, listVersionsConfig.Format) - } - - return nil -} diff --git a/cmd/hi/main.go b/cmd/hi/main.go index 903d77fa1..2bbfefe04 100644 --- a/cmd/hi/main.go +++ b/cmd/hi/main.go @@ -29,13 +29,6 @@ func main() { return runDoctorCheck(env.Context()) }, }, - { - Name: "list-versions", - Help: "Print Tailscale versions used by integration tests", - Usage: "list-versions [flags]", - SetFlags: command.Flags(flax.MustBind, &listVersionsConfig), - Run: listVersions, - }, { Name: "clean", Help: "Clean Docker resources", @@ -86,17 +79,20 @@ func main() { } func cleanAll(ctx context.Context) error { - for _, step := range []func(context.Context) error{ - killTestContainers, - pruneDockerNetworks, - cleanOldImages, - cleanCacheVolume, - } { - err := step(ctx) - if err != nil { - return err - } + err := killTestContainers(ctx) + if err != nil { + return err } - return nil + err = pruneDockerNetworks(ctx) + if err != nil { + return err + } + + err = cleanOldImages(ctx) + if err != nil { + return err + } + + return cleanCacheVolume(ctx) } diff --git a/cmd/hi/run.go b/cmd/hi/run.go index 35214e3cf..c297c6d83 100644 --- a/cmd/hi/run.go +++ b/cmd/hi/run.go @@ -6,7 +6,6 @@ import ( "log" "os" "path/filepath" - "strings" "time" "github.com/creachadair/command" @@ -67,21 +66,64 @@ func runIntegrationTest(env *command.Env) error { // detectGoVersion reads the Go version from go.mod file. func detectGoVersion() string { - content, err := os.ReadFile("go.mod") - if err != nil { - content, err = os.ReadFile(filepath.Join("..", "..", "go.mod")) - if err != nil { - return "1.26.1" - } + goModPath := filepath.Join("..", "..", "go.mod") + + if _, err := os.Stat("go.mod"); err == nil { //nolint:noinlineerr + goModPath = "go.mod" + } else if _, err := os.Stat("../../go.mod"); err == nil { //nolint:noinlineerr + goModPath = "../../go.mod" } - for line := range strings.Lines(string(content)) { - if rest, ok := strings.CutPrefix(line, "go "); ok { - if f := strings.Fields(rest); len(f) > 0 { - return f[0] + content, err := os.ReadFile(goModPath) + if err != nil { + return "1.26.1" + } + + lines := splitLines(string(content)) + for _, line := range lines { + if len(line) > 3 && line[:3] == "go " { + version := line[3:] + if idx := indexOf(version, " "); idx != -1 { + version = version[:idx] } + + return version } } return "1.26.1" } + +// splitLines splits a string into lines without using strings.Split. +func splitLines(s string) []string { + var ( + lines []string + current string + ) + + for _, char := range s { + if char == '\n' { + lines = append(lines, current) + current = "" + } else { + current += string(char) + } + } + + if current != "" { + lines = append(lines, current) + } + + return lines +} + +// indexOf finds the first occurrence of substr in s. +func indexOf(s, substr string) int { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return i + } + } + + return -1 +} diff --git a/cmd/hi/stats.go b/cmd/hi/stats.go index f56043d96..8ab8b7b3c 100644 --- a/cmd/hi/stats.go +++ b/cmd/hi/stats.go @@ -1,13 +1,12 @@ package main import ( - "cmp" "context" "encoding/json" "errors" "fmt" "log" - "slices" + "sort" "strings" "sync" "time" @@ -161,7 +160,7 @@ func (sc *StatsCollector) monitorDockerEvents(ctx context.Context, runID string, continue } - // Convert to [types.Container] format for consistency + // Convert to types.Container format for consistency cont := types.Container{ //nolint:staticcheck // SA1019: use container.Summary ID: containerInfo.ID, Names: []string{containerInfo.Name}, @@ -257,7 +256,7 @@ func (sc *StatsCollector) collectStatsForContainer(ctx context.Context, containe err := decoder.Decode(&stats) if err != nil { - // [io.EOF] is expected when container stops or stream ends + // EOF is expected when container stops or stream ends if err.Error() != "EOF" && verbose { log.Printf("Failed to decode stats for container %s: %v", containerID[:12], err) } @@ -313,7 +312,7 @@ func calculateCPUPercent(prevStats, stats *container.Stats) float64 { //nolint:s // Calculate CPU percentage: (container CPU delta / system CPU delta) * number of CPUs * 100 numCPUs := float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) if numCPUs == 0 { - // Fallback: if [PercpuUsage] is not available, assume 1 CPU + // Fallback: if PercpuUsage is not available, assume 1 CPU numCPUs = 1.0 } @@ -375,24 +374,24 @@ func (sc *StatsCollector) GetSummary() []ContainerStatsSummary { SampleCount: len(stats), } - extract := func(get func(StatsSample) float64) []float64 { - values := make([]float64, len(stats)) - for i, sample := range stats { - values[i] = get(sample) - } + // Calculate CPU stats + cpuValues := make([]float64, len(stats)) + memoryValues := make([]float64, len(stats)) - return values + for i, sample := range stats { + cpuValues[i] = sample.CPUUsage + memoryValues[i] = sample.MemoryMB } - summary.CPU = calculateStatsSummary(extract(func(s StatsSample) float64 { return s.CPUUsage })) - summary.Memory = calculateStatsSummary(extract(func(s StatsSample) float64 { return s.MemoryMB })) + summary.CPU = calculateStatsSummary(cpuValues) + summary.Memory = calculateStatsSummary(memoryValues) summaries = append(summaries, summary) } // Sort by container name for consistent output - slices.SortFunc(summaries, func(a, b ContainerStatsSummary) int { - return cmp.Compare(a.ContainerName, b.ContainerName) + sort.Slice(summaries, func(i, j int) bool { + return summaries[i].ContainerName < summaries[j].ContainerName }) return summaries @@ -409,8 +408,14 @@ func calculateStatsSummary(values []float64) StatsSummary { sum := 0.0 for _, value := range values { - minVal = min(minVal, value) - maxVal = max(maxVal, value) + if value < minVal { + minVal = value + } + + if value > maxVal { + maxVal = value + } + sum += value } diff --git a/cmd/vendorhash/main.go b/cmd/vendorhash/main.go index 2a385c125..ca6e35fce 100644 --- a/cmd/vendorhash/main.go +++ b/cmd/vendorhash/main.go @@ -12,7 +12,7 @@ // vendorhash check exit non-zero if flakehashes.json is stale // vendorhash update recompute and rewrite flakehashes.json // -// The JSON schema and [goModFingerprint] algorithm mirror upstream +// The JSON schema and goModFingerprint algorithm mirror upstream // tailscale's tool/updateflakes so a future shared library extraction // is straightforward. package main @@ -82,8 +82,8 @@ func usage() { fmt.Fprintln(os.Stderr, "usage: vendorhash ") } -// errStale signals to [main] that the check found a mismatch; it has -// already printed a remediation message, so [main] should exit 1 +// errStale signals to main that the check found a mismatch; it has +// already printed a remediation message, so main should exit 1 // silently. var errStale = errors.New("vendor hash stale") diff --git a/config-example.yaml b/config-example.yaml index 09f0356b8..b7870d527 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -23,12 +23,21 @@ listen_addr: 127.0.0.1:8080 # Use an empty value to disable the metrics listener. metrics_listen_addr: 127.0.0.1:9090 -# CIDR(s) of reverse proxies (e.g. 127.0.0.1/32) whose -# True-Client-IP, X-Real-IP and X-Forwarded-For headers should -# be honoured. Empty (default) ignores those headers; setting -# this without a proxy in front lets clients spoof their logged -# source IP. -trusted_proxies: [] +# Address to listen for gRPC. +# gRPC is used for controlling a headscale server +# remotely with the CLI +# Note: Remote access _only_ works if you have +# valid certificates. +# +# For production: +# grpc_listen_addr: 0.0.0.0:50443 +grpc_listen_addr: 127.0.0.1:50443 + +# Allow the gRPC admin interface to run in INSECURE +# mode. This is not recommended as the traffic will +# be unencrypted. Only enable if you know what you +# are doing. +grpc_allow_insecure: false # The Noise section includes specific configuration for the # TS2021 Noise protocol @@ -71,7 +80,8 @@ prefixes: # connection cannot be established. # https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp # -# Headscale needs a list of DERP servers that can be presented to the clients. +# headscale needs a list of DERP servers that can be presented +# to the clients. derp: server: # If enabled, runs the embedded DERP server and merges it into the rest of the DERP config @@ -116,9 +126,9 @@ derp: # Locally available DERP map files encoded in YAML # - # This option is mostly interesting for people hosting their own DERP servers: + # This option is mostly interesting for people hosting + # their own DERP servers: # https://tailscale.com/docs/reference/derp-servers/custom-derp-servers - # https://headscale.net/stable/ref/derp/ # # paths: # - /etc/headscale/derp-example.yaml @@ -230,12 +240,12 @@ database: # ssl: false ### TLS configuration -# See: https://headscale.net/stable/ref/tls/ - -## Let's Encrypt / ACME -# Headscale supports automatically requesting and setting up +# +## Let's encrypt / ACME +# +# headscale supports automatically requesting and setting up # TLS for a domain with Let's Encrypt. - +# # URL to ACME directory acme_url: https://acme-v02.api.letsencrypt.org/directory @@ -245,13 +255,15 @@ acme_email: "" # Domain name to request a TLS certificate for: tls_letsencrypt_hostname: "" -# Path to store certificates and metadata needed by letsencrypt +# Path to store certificates and metadata needed by +# letsencrypt +# For production: tls_letsencrypt_cache_dir: /var/lib/headscale/cache # Type of ACME challenge to use, currently supported types: # HTTP-01 or TLS-ALPN-01 +# See: docs/ref/tls.md for more information tls_letsencrypt_challenge_type: HTTP-01 - # When HTTP-01 challenge is chosen, letsencrypt must set up a # verification endpoint, and it will be listening on: # :http = port 80 @@ -277,7 +289,6 @@ policy: # The mode can be "file" or "database" that defines # where the policies are stored and read from. mode: file - # If the mode is set to "file", the path to a HuJSON file containing policies. path: "" @@ -286,6 +297,7 @@ policy: # headscale supports Tailscale's DNS configuration and MagicDNS. # Please have a look to their docs to better understand the concepts: # +# - https://tailscale.com/docs/features/access-control/acls # - https://tailscale.com/docs/features/magicdns # - https://tailscale.com/blog/2021-09-private-dns-with-magicdns # @@ -302,7 +314,7 @@ policy: # If you want stop Headscale from managing the DNS configuration # all the fields under `dns` should be set to empty values. dns: - # Whether to use MagicDNS + # Whether to use [MagicDNS](https://tailscale.com/docs/features/magicdns). magic_dns: true # Defines the base domain to create the hostnames for MagicDNS. @@ -343,7 +355,7 @@ dns: # Extra DNS records # so far only A and AAAA records are supported (on the tailscale side) - # See: https://headscale.net/stable/ref/dns/ + # See: docs/ref/dns.md extra_records: [] # - name: "grafana.myvpn.example.com" # type: "A" @@ -362,7 +374,6 @@ unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770" # OpenID Connect -# https://headscale.net/stable/ref/oidc/ # oidc: # # Block startup until the identity provider is available and healthy. # only_start_if_oidc_is_available: true @@ -459,7 +470,6 @@ taildrop: # choice. auto_update: enabled: false - # Advanced performance tuning parameters. # The defaults are carefully chosen and should rarely need adjustment. # Only modify these if you have identified a specific performance issue. diff --git a/docs/about/faq.md b/docs/about/faq.md index 25cdb1123..09792653d 100644 --- a/docs/about/faq.md +++ b/docs/about/faq.md @@ -152,15 +152,15 @@ See also . Headscale checks if the policy is valid during startup and refuses to start if it detects an error. The error message indicates which part of the policy is invalid. Follow these steps to fix your policy: -- Dump the policy to a file: `headscale policy get --bypass-server-and-access-database-directly > policy.json` +- Dump the policy to a file: `headscale policy get --bypass-grpc-and-access-database-directly > policy.json` - Edit and fixup `policy.json`. Use the command `headscale policy check --file policy.json` to validate the policy. -- Load the modified policy: `headscale policy set --bypass-server-and-access-database-directly --file policy.json` +- Load the modified policy: `headscale policy set --bypass-grpc-and-access-database-directly --file policy.json` - Start Headscale as usual. !!! warning "Full server configuration required" The above commands to get/set the policy require a complete server configuration file including database settings. A - minimal config to [control Headscale via remote CLI](../ref/api.md#remote-control) is not sufficient. You may use + minimal config to [control Headscale via remote CLI](../ref/api.md#grpc) is not sufficient. You may use `headscale -c /path/to/config.yaml` to specify the path to an alternative configuration file. ## How can I migrate back to the recommended IP prefixes? diff --git a/docs/about/features.md b/docs/about/features.md index e92327c8c..675c0ea04 100644 --- a/docs/about/features.md +++ b/docs/about/features.md @@ -13,9 +13,7 @@ provides on overview of Headscale's feature and compatibility with the Tailscale - [x] [Global and restricted nameservers (split DNS)](https://tailscale.com/docs/reference/dns-in-tailscale#nameservers) - [x] [search domains](https://tailscale.com/docs/reference/dns-in-tailscale#search-domains) - [x] [Extra DNS records (Headscale only)](../ref/dns.md#setting-extra-dns-records) -- [x] File sharing - - [x] [Taildrive](https://tailscale.com/docs/features/taildrive) - - [x] [Taildrop](https://tailscale.com/docs/features/taildrop) +- [x] [Taildrop](https://tailscale.com/docs/features/taildrop) - [x] [Tags](../ref/tags.md) - [x] [Routes](../ref/routes.md) - [x] [Subnet routers](../ref/routes.md#subnet-router) @@ -33,9 +31,6 @@ provides on overview of Headscale's feature and compatibility with the Tailscale routers](../ref/routes.md#automatically-approve-routes-of-a-subnet-router) and [exit nodes](../ref/routes.md#automatically-approve-an-exit-node-with-auto-approvers) - [x] [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh) - - [x] [Node attributes](../ref/policy.md#node-attributes) - - [x] [Tests](https://tailscale.com/docs/reference/syntax/policy-file#tests) and - [sshTests](https://tailscale.com/docs/reference/syntax/policy-file#ssh-tests) - [x] [Node registration using Single-Sign-On (OpenID Connect)](../ref/oidc.md) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC)) - [x] Basic registration - [x] Update user profile from identity provider diff --git a/docs/ref/api.md b/docs/ref/api.md index 8f492dfd3..4337da137 100644 --- a/docs/ref/api.md +++ b/docs/ref/api.md @@ -1,10 +1,10 @@ # API -Headscale provides a [HTTP REST API](#rest-api) which may be used to integrate a [web -interface](integration/web-ui.md), [remote control Headscale](#remote-control) or provide a base for custom +Headscale provides a [HTTP REST API](#rest-api) and a [gRPC interface](#grpc) which may be used to integrate a [web +interface](integration/web-ui.md), [remote control Headscale](#setup-remote-control) or provide a base for custom integration and tooling. -The API requires a valid API key before use. To create an API key, log into your Headscale server and generate +Both interfaces require a valid API key before use. To create an API key, log into your Headscale server and generate one with the default expiration of 90 days: ```shell @@ -29,12 +29,12 @@ headscale apikeys expire --prefix ## REST API - API endpoint: `/api/v1`, e.g. `https://headscale.example.com/api/v1` -- Documentation: `/api/v1/docs`, e.g. `https://headscale.example.com/api/v1/docs` +- Documentation: `/swagger`, e.g. `https://headscale.example.com/swagger` - Headscale Version: `/version`, e.g. `https://headscale.example.com/version` - Authenticate using HTTP Bearer authentication by sending the [API key](#api) with the HTTP `Authorization: Bearer ` header. Start by [creating an API key](#api) and test it with the examples below. Read the API documentation provided by your -Headscale server at `/api/v1/docs` for details. +Headscale server at `/swagger` for details. === "Get details for all users" @@ -54,18 +54,20 @@ Headscale server at `/api/v1/docs` for details. ```console curl -H "Authorization: Bearer " \ - --json '{"user": "", "authId": ""}' \ + --json '{"user": "", "authId": "AUTH_ID>"}' \ https://headscale.example.com/api/v1/auth/register ``` -## Remote control +## gRPC -The `headscale` binary can control a Headscale instance from a remote machine over the HTTP API. +The gRPC interface can be used to control a Headscale instance from a remote machine with the `headscale` binary. ### Prerequisite - A workstation to run `headscale` (any supported platform, e.g. Linux). -- The Headscale server reachable over HTTP(S). +- A Headscale server with gRPC enabled. +- Connections to the gRPC port (default: `50443`) are allowed. +- Remote access requires an encrypted connection via TLS. - An [API key](#api) to authenticate with the Headscale server. ### Setup remote control @@ -86,20 +88,19 @@ The `headscale` binary can control a Headscale instance from a remote machine ov ```yaml title="config.yaml" cli: - address: + address: : api_key: ``` === "Environment variables" ```shell - export HEADSCALE_CLI_ADDRESS="" + export HEADSCALE_CLI_ADDRESS=":" export HEADSCALE_CLI_API_KEY="" ``` - This instructs the `headscale` binary to connect to a remote instance at `` (e.g. - `https://headscale.example.com`), instead of connecting to the local instance. A bare host without a scheme is - assumed to be `https`. + This instructs the `headscale` binary to connect to a remote instance at `:`, instead of + connecting to the local instance. 1. Test the connection by listing all nodes: @@ -112,12 +113,15 @@ The `headscale` binary can control a Headscale instance from a remote machine ov ### Behind a proxy -The remote CLI uses the same HTTP API as everything else, so it works through the reverse proxy already in front of -Headscale with no extra setup. +It's possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as Headscale. + +While this is _not a supported_ feature, an example on how this can be set up on +[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91). ### Troubleshooting - Make sure you have the _same_ Headscale version on your server and workstation. +- Ensure that connections to the gRPC port are allowed. - Verify that your TLS certificate is valid and trusted. - If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either: - Add your self-signed certificate to the trust store of your OS _or_ diff --git a/docs/ref/integration/reverse-proxy.md b/docs/ref/integration/reverse-proxy.md index 2729786a5..3586171fd 100644 --- a/docs/ref/integration/reverse-proxy.md +++ b/docs/ref/integration/reverse-proxy.md @@ -1,183 +1,90 @@ -# Running Headscale behind a reverse proxy +# Running headscale behind a reverse proxy !!! warning "Community documentation" - This page is not actively maintained by the Headscale authors and is - written by community members. It is _not_ verified by Headscale developers. + This page is not actively maintained by the headscale authors and is + written by community members. It is _not_ verified by headscale developers. **It might be outdated and it might miss necessary steps**. -Running Headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want -to reuse the same external IP and port - usually tcp/443 for HTTPS. +Running headscale behind a reverse proxy is useful when running multiple applications on the same server, and you want to reuse the same external IP and port - usually tcp/443 for HTTPS. -Please see [limitations](#limitations) for known issues and limitations. +### WebSockets -## Configuration +The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients. -The configuration depends on the set of Headscale features you intend to use. Please have a look at the -[requirements](../../setup/requirements.md) and especially the [ports in use](../../setup/requirements.md#ports-in-use) -section to learn what a Tailscale clients expects. - -The configuration examples in this documentation are basic and cover only HTTP and HTTPS traffic. Other features such as -STUN for Headscale's [embedded DERP server](../derp.md) are expected to be exposed directly or to be only available on -localhost. - -### WebSocket - -Tailscale clients are using a custom protocol (Tailscale Control Protocol) to communicate with a control server such as -Headscale. The reverse proxy **must** be configured to support WebSockets in order to communicate with Tailscale clients -and it needs to handle two peculiarities of the Tailscale Control Protocol: - -- The POST method is used to upgrade the WebSocket connection. -- The value for the `Upgrade` header is `tailscale-control-protocol`. - -### TLS - -Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration -values to your Headscale [configuration file](../configuration.md): - -```yaml title="config.yaml" hl_lines="1" -server_url: https:// -tls_cert_path: "" -tls_key_path: "" -``` - -Headscale logs `WRN listening without TLS but ServerURL does not start with http://` during startup. This is expected -and indicates that the reverse proxy is in charge of terminating TLS. - -### Trusted proxies - -Headscale ignores `True-Client-IP`, `X-Real-IP` and `X-Forwarded-For` headers unless the request's TCP peer matches the -`trusted_proxies` configuration option. Set this to the CIDR(s) your reverse proxy connects from so the real client IP -appears in access logs. - -```yaml title="config.yaml" -trusted_proxies: - - 127.0.0.1/32 - - ::1/128 -``` - -The reverse proxy is responsible to replace any client-supplied `True-Client-IP`, `X-Real-IP`, `X-Forwarded-For` headers -on inbound requests with sanitized values. Headscale picks the first valid IP address supplied by headers in this order: - -- `True-Client-IP` -- `X-Real-IP` -- `X-Forwarded-For` - -## Limitations - -- A reverse proxy adds another layer of complexity that needs to be able to handle the [Tailscale Control - Protocol](#websocket) properly. Be sure to test your setup without a reverse proxy before raising an issue. -- STUN (used along with the [embedded DERP server](../derp.md)) requires udp/3478 to be served publicly. - -## Reverse proxy specific configuration - -!!! warning "Third-party software and services" - - This section of the documentation is specific for third-party software and services. We recommend users read the - third-party documentation for a secure configuration. - -This following Headscale configuration may be used as base for the various reverse proxy examples below. The following -is [assumed](../../setup/requirements.md): - -- Service for Tailscale clients is served via HTTPS on port 443. -- The reverse proxy redirects HTTP to HTTPS and is terminating TLS. -- Both Headscale and the reverse proxy are running on the same host. -- [Metrics](../debug.md#metrics-and-debug-endpoint) are not proxied, those are available via localhost. - -```yaml title="config.yaml" hl_lines="1" -server_url: https:// -listen_addr: 127.0.0.1:8080 -metrics_listen_addr: 127.0.0.1:9090 -trusted_proxies: - - 127.0.0.1/32 - - ::1/128 -tls_cert_path: "" -tls_key_path: "" -``` - -### Apache - -The following basic Apache configuration works with the Headscale configuration [as shown -above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed: - -- ``: The server name for your instance, e.g. `headscale.example.com` -- ``: Absolute path to your TLS certificate -- ``: Absolute path to your TLS private key - -```apache title="apache.conf" hl_lines="2 7 11 14-15" - - ServerName - - # Tailscale captive portal detection - RedirectMatch 204 ^/generate_204$ - - RedirectMatch permanent "^/(.*)$" "https:///$1" - - - - ServerName - - SSLEngine On - SSLCertificateFile - SSLCertificateKeyFile - - RequestHeader set True-Client-IP "%{REMOTE_ADDR}s" - RequestHeader set X-Real-IP "%{REMOTE_ADDR}s" - - ProxyPreserveHost On - ProxyPass / http://127.0.0.1:8080/ upgrade=any - -``` - -Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSocket traffic whose `Upgrade` header -value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache -docs](https://httpd.apache.org/docs/current/mod/mod_proxy.html#upgrade) for more information on this. - -### Caddy - -The following basic Caddyfile works with the Headscale configuration [as shown -above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed: - -- ``: The server name for your instance, e.g. `headscale.example.com` - -```none title="Caddyfile" hl_lines="1 12" -http:// { - # Tailscale captive portal detection - handle /generate_204 { - respond 204 - } - - handle * { - redir https://{host}{uri} - } -} - - { - reverse_proxy 127.0.0.1:8080 { - header_up True-Client-IP {remote_host} - header_up X-Real-IP {remote_host} - } -} -``` - -Caddy will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your -domain/subdomain, force HTTPS, and proxy WebSocket connections. +WebSockets support is also required when using the Headscale [embedded DERP server](../derp.md). In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml). ### Cloudflare -Running Headscale behind a Cloudflare Proxy or Cloudflare Tunnel is not supported and will not work as Cloudflare does -not support [WebSocket POSTs as required by the Tailscale protocol](#websocket). See [issue -1468](https://github.com/juanfont/headscale/issues/1468) for more information. +Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See [this issue](https://github.com/juanfont/headscale/issues/1468) + +### TLS + +Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file. + +```yaml title="config.yaml" +server_url: https:// # This should be the FQDN at which headscale will be served +listen_addr: 0.0.0.0:8080 +metrics_listen_addr: 0.0.0.0:9090 +tls_cert_path: "" +tls_key_path: "" +``` + +## nginx + +The following example configuration can be used in your nginx setup, substituting values as necessary. `` should be the IP address and port where headscale is running. In most cases, this will be `http://localhost:8080`. + +```nginx title="nginx.conf" +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + listen [::]:80; + + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name ; + + ssl_certificate ; + ssl_certificate_key ; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass http://; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $server_name; + proxy_redirect http:// https://; + proxy_buffering off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + } +} +``` + +## istio/envoy + +If you using [Istio](https://istio.io/) ingressgateway or [Envoy](https://www.envoyproxy.io/) as reverse proxy, there are some tips for you. If not set, you may see some debug log in proxy as below: + +```log +Sending local reply with details upgrade_failed +``` ### Envoy -You need to add a new upgrade_type named `tailscale-control-protocol`. [See -details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-upgradeconfig). +You need to add a new upgrade_type named `tailscale-control-protocol`. [see details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-upgradeconfig) ### Istio -Same as [envoy](#envoy), we can use `EnvoyFilter` to add a new upgrade_type named `tailscale-control-protocol`. +Same as envoy, we can use `EnvoyFilter` to add upgrade_type. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -202,68 +109,33 @@ spec: - upgrade_type: tailscale-control-protocol ``` -### Nginx +## Caddy -The following basic Nginx configuration works with the Headscale configuration [as shown -above](#reverse-proxy-specific-configuration). Substitute placeholders and adjust the configuration as needed: +The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the `config.yaml` specifications above to disable headscale's built in TLS. Replace values as necessary - `` should be the FQDN at which headscale will be served, and `` should be the IP address and port where headscale is running. In most cases, this will be `localhost:8080`. -- ``: The server name for your instance, e.g. `headscale.example.com` -- ``: Absolute path to your TLS certificate -- ``: Absolute path to your TLS private key - -```nginx title="nginx.conf" hl_lines="19 37 39-40" -# headscale -upstream headscale { - zone upstreams 64K; - server 127.0.0.1:8080 max_fails=1 fail_timeout=5s; - keepalive 2; -} - -# websocket -map $http_upgrade $connection_upgrade { - default keep-alive; - '' close; -} - -# http -server { - listen 80; - listen [::]:80; - - server_name ; - - # Tailscale captive portal detection - location = /generate_204 { - return 204; - } - - location / { - return 301 https://$server_name$request_uri; - } -} - -# https -server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - - server_name ; - - ssl_certificate ; - ssl_certificate_key ; - - location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Host $host; - proxy_set_header True-Client-IP $remote_addr; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_buffering off; - proxy_pass http://headscale; - } +```none title="Caddyfile" + { + reverse_proxy } ``` + +Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary. + +For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference. + +## Apache + +The following minimal Apache config will proxy traffic to the headscale instance on ``. Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSockets traffic whose `Upgrade` header value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache docs](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) for more information on this. + +```apache title="apache.conf" + + ServerName + + ProxyPreserveHost On + ProxyPass / http:/// upgrade=any + + SSLEngine On + SSLCertificateFile + SSLCertificateKeyFile + +``` diff --git a/docs/ref/integration/web-ui.md b/docs/ref/integration/web-ui.md index 12aa6d373..8f00695a8 100644 --- a/docs/ref/integration/web-ui.md +++ b/docs/ref/integration/web-ui.md @@ -23,7 +23,5 @@ Headscale doesn't provide a built-in web interface but users may pick one from t - [HeadControl](https://github.com/ahmadzip/HeadControl) - Minimal Headscale admin dashboard, built with Go and HTMX - [Headscale Manager](https://github.com/hkdone/headscalemanager) - Headscale UI for Android - [Headscale UI](https://github.com/MunMunMiao/headscale-ui) - Headscale UI online and Self-hosting -- [Headscale Panel](https://github.com/headscale-panel/panel) - A modern Headscale management panel with a clean, - network-operations-focused UI You can ask for support on our [Discord server](https://discord.gg/c84AZQhmpx) in the "web-interfaces" channel. diff --git a/docs/ref/policy.md b/docs/ref/policy.md index f326d7ecb..10248e76b 100644 --- a/docs/ref/policy.md +++ b/docs/ref/policy.md @@ -158,17 +158,17 @@ devices. Can only be used in policy destinations. { "src": ["boss@"], "dst": ["boss@"], - "ip": ["*"] + "ip": "*" }, { "src": ["dev1@"], "dst": ["dev1@"], - "ip": ["*"] + "ip": "*" }, { "src": ["intern1@"], "dst": ["intern1@"], - "ip": ["*"] + "ip": "*" } ] } @@ -196,54 +196,5 @@ Used in Tailscale SSH rules to allow access to any user except root. Can only be This autogroup resolves to all IP addresses (`0.0.0.0/0` and `::/0`) which also includes all IP addresses outside the standard Tailscale IP ranges. This autogroup can only be used as source. -## Node Attributes - -[Node attributes](https://tailscale.com/docs/reference/syntax/policy-file#node-attributes) allow for device-specific -configuration and attributes. At least the following node attributes are currently supported by Headscale[^2]: - -- `drive:access`, `drive:share`: [Taildrive support](https://tailscale.com/docs/features/taildrive). -- `nextdns:`, `nextdns:no-device-info`: [NextDNS integration](https://tailscale.com/docs/integrations/nextdns). - Be sure to set NextDNS as global resolver in the [configuration](configuration.md). -- `magicdns-aaaa`: Respond to AAAA queries on the local [MagicDNS](https://tailscale.com/docs/features/magicdns) - resolver at 100.100.100.100. -- `disable-ipv4`: Selectively disable IPv4 for specfic nodes. This is may be useful to workaround [CGNat - conflicts](https://tailscale.com/docs/reference/troubleshooting/network-configuration/cgnat-conflicts). -- `randomize-client-port`: Allocate a [random port for WireGuard - traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default - port 41641. -- `disable-captive-portal-detection`: [Disable automatic captive portal - detection](https://tailscale.com/docs/integrations/captive-portals#disable-captive-portal-detection). - -```json title="policy.json" -{ - "nodeAttrs": [ - { - // Enable MagicDNS AAAA records for all nodes - "target": ["*"] - "attr": ["magicdns-aaaa"] - } - ] -} -``` - -## Network-wide policy options - -The following options are applied for the entire tailnet. Consider [node attributes](#node-attributes) for a more -fine-grained configuration instead. - -- `randomizeClientPort`: Allocate a [random port for WireGuard - traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default - port 41641. - -```json title="policy.json" -{ - // Use a random WireGuard port for the entire tailnet - "randomizeClientPort": true -} -``` - [^1]: Headscale also allows to store the policy in the database. This is typically only required in case a [web interface](integration/web-ui.md) is used. - -[^2]: Other key-only node attributes can be used as well. Find them in the client source code with `grep -E '^\s+NodeAttr\w+' tailcfg/tailcfg.go` or by using [GitHub code search (requires - login)](https://github.com/search?q=repo%3Atailscale%2Ftailscale%20language%3Ago%20path%3Atailcfg%2Ftailcfg.go%20symbol%3A%2FNodeAttr%5Cw%2B%2F&type=code). diff --git a/docs/setup/install/official.md b/docs/setup/install/official.md index 52988836d..a70422f8f 100644 --- a/docs/setup/install/official.md +++ b/docs/setup/install/official.md @@ -31,10 +31,10 @@ distributions are Ubuntu 22.04 or newer, Debian 12 or newer. sudo nano /etc/headscale/config.yaml ``` -1. Restart headscale to pick up configuration changes: +1. Enable and start the headscale service: ```shell - sudo systemctl restart headscale + sudo systemctl enable --now headscale ``` 1. Verify that headscale is running as intended: diff --git a/docs/setup/requirements.md b/docs/setup/requirements.md index da84ae856..6c11386c3 100644 --- a/docs/setup/requirements.md +++ b/docs/setup/requirements.md @@ -26,6 +26,9 @@ The ports in use vary with the intended scenario and enabled features. Some of t - udp/3478 - Expose publicly: yes - STUN, required if the [embedded DERP server](../ref/derp.md) is enabled +- tcp/50443 + - Expose publicly: yes + - Only required if the gRPC interface is used to [remote-control Headscale](../ref/api.md#grpc). - tcp/9090 - Expose publicly: no - [Metrics and debug endpoint](../ref/debug.md#metrics-and-debug-endpoint) diff --git a/docs/setup/upgrade.md b/docs/setup/upgrade.md index 8176e933a..e12359cf3 100644 --- a/docs/setup/upgrade.md +++ b/docs/setup/upgrade.md @@ -2,7 +2,7 @@ !!! tip "Required update path" - It's required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without skipping minor + Its required to update from one stable version to the next (e.g. 0.26.0 → 0.27.1 → 0.28.0) without skipping minor versions in between. You should always pick the latest available patch release. Update an existing Headscale installation to a new version: @@ -23,7 +23,7 @@ upgrading. A full backup of Headscale depends on your individual setup, but belo === "Standard installation" - An installation that follows our [official releases](install/official.md) setup guide uses the following paths: + A installation that follows our [official releases](install/official.md) setup guide uses the following paths: - [Configuration file](../ref/configuration.md): `/etc/headscale/config.yaml` - Data directory: `/var/lib/headscale` @@ -37,7 +37,7 @@ upgrading. A full backup of Headscale depends on your individual setup, but belo === "Container" - An installation that follows our [container](install/container.md) setup guide uses a single source volume directory + A installation that follows our [container](install/container.md) setup guide uses a single source volume directory that contains the configuration file, data directory and the SQLite database. ```console diff --git a/flake.lock b/flake.lock index c723d287a..339d9dd1c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,27 +1,5 @@ { "nodes": { - "flake-checks": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": [ - "nixpkgs" - ], - "treefmt-nix": "treefmt-nix" - }, - "locked": { - "lastModified": 1781951072, - "narHash": "sha256-hA9u6hB4QzpReP8hudxqiaa4vLhoRvtXi6YmUgJRGEs=", - "owner": "kradalby", - "repo": "flake-checks", - "rev": "3d2882efec5cf10f8b5a8a035d93ba6ba9f21717", - "type": "github" - }, - "original": { - "owner": "kradalby", - "repo": "flake-checks", - "type": "github" - } - }, "flake-utils": { "inputs": { "systems": "systems" @@ -40,44 +18,25 @@ "type": "github" } }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1781153106, - "narHash": "sha256-yzsroLCcuRG4KdGMxWt0eXKOrRSgQT8/xjYngeq9ujU=", + "lastModified": 1777270315, + "narHash": "sha256-yKB4G6cKsQsWN7M6rZGk6gkJPDNPIzT05y4qzRyCDlI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9ee75f111a06d7ab2b2f729698a8eff53d54e070", + "rev": "6368eda62c9775c38ef7f714b2555a741c20c72d", "type": "github" }, "original": { "owner": "NixOS", - "ref": "staging-next-26.05", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "flake-checks": "flake-checks", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } }, @@ -95,42 +54,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "treefmt-nix": { - "inputs": { - "nixpkgs": [ - "flake-checks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1780220602, - "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "db947814a175b7ca6ded66e21383d938df01c227", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 96786def3..ef0b40b86 100644 --- a/flake.nix +++ b/flake.nix @@ -2,24 +2,14 @@ description = "headscale - Open Source Tailscale Control server"; inputs = { - # Pinned to staging-next-26.05 for Go 1.26.4 (security fix GO-2026-5037/5039): - # nixpkgs-unstable still ships 1.26.3 — the bump is merged to nixpkgs staging - # but the large-rebuild staging->unstable pipeline lags. The 26.05 line is - # otherwise current (dev tools match unstable). Switch back to nixpkgs-unstable - # once it ships go_1_26 >= 1.26.4. - nixpkgs.url = "github:NixOS/nixpkgs/staging-next-26.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; - # Reusable Go flake checks (build/test/lint/format); CI runs them via - # `nix build .#checks..` instead of bespoke per-tool steps. - flake-checks.url = "github:kradalby/flake-checks"; - flake-checks.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = { self , nixpkgs , flake-utils - , flake-checks , ... }: let @@ -36,7 +26,6 @@ overlays.default = _: prev: let pkgs = nixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system}; - # Go 1.26 builder; resolves to Go 1.26.4 from the pinned nixpkgs. buildGo = pkgs.buildGo126Module; vendorHash = (builtins.fromJSON (builtins.readFile ./flakehashes.json)).vendor.sri; in @@ -71,20 +60,53 @@ subPackages = [ "cmd/hi" ]; }; - # Build golangci-lint with stock Go 1.26 (upstream uses hardcoded Go - # version); it does not build against the pinned 1.26.4. + protoc-gen-grpc-gateway = buildGo rec { + pname = "grpc-gateway"; + version = "2.28.0"; + + src = pkgs.fetchFromGitHub { + owner = "grpc-ecosystem"; + repo = "grpc-gateway"; + rev = "v${version}"; + sha256 = "sha256-93omvHb+b+S0w4D+FGEEwYYDjgumJFDAruc1P4elfvA="; + }; + + vendorHash = "sha256-jVP5zfFPfHeAEApKNJzZwuZLA+DjKgkL7m2DFG72UNs="; + + nativeBuildInputs = [ pkgs.installShellFiles ]; + + subPackages = [ "protoc-gen-grpc-gateway" "protoc-gen-openapiv2" ]; + }; + + protobuf-language-server = buildGo rec { + pname = "protobuf-language-server"; + version = "ab4c128"; + + src = pkgs.fetchFromGitHub { + owner = "lasorda"; + repo = "protobuf-language-server"; + rev = "ab4c128f00774d51bd6d1f4cfa735f4b7c8619e3"; + sha256 = "sha256-yF6kG+qTRxVO/qp2V9HgTyFBeOm5RQzeqdZFrdidwxM="; + }; + + vendorHash = "sha256-4nTpKBe7ekJsfQf+P6edT/9Vp2SBYbKz1ITawD3bhkI="; + + subPackages = [ "." ]; + }; + + # Build golangci-lint with Go 1.26 (upstream uses hardcoded Go version) golangci-lint = buildGo rec { pname = "golangci-lint"; - version = "2.12.2"; + version = "2.11.4"; src = pkgs.fetchFromGitHub { owner = "golangci"; repo = "golangci-lint"; rev = "v${version}"; - hash = "sha256-qR7fp1x2S+EwEAcplRHTvA3jWwLr/XSiYKSZtAwkrNU="; + hash = "sha256-B19aLvfNRY9TOYw/71f2vpNUuSIz8OI4dL0ijGezsas="; }; - vendorHash = "sha256-AG5wtLwWLz55bdp1oi3cW+9O3yj1W1P7MV9zxym7Pb4="; + vendorHash = "sha256-xuoj4+U4tB5gpABKq4Dbp2cxnljxdYoBbO8A7DqPM5E="; subPackages = [ "cmd/golangci-lint" ]; @@ -157,11 +179,6 @@ yq-go ripgrep postgresql - - # External clients exercised by the Tailscale-compatible v2 API - # roundtrip tests (TestAPIv2). Binaries: tofu, tscli. - opentofu - tscli python314Packages.mdformat python314Packages.mdformat-footnote python314Packages.mdformat-frontmatter @@ -171,8 +188,17 @@ # 'dot' is needed for pprof graphs # go tool pprof -http=: graphviz + + # Protobuf dependencies + protobuf + protoc-gen-go + protoc-gen-go-grpc + protoc-gen-grpc-gateway + buf + clang-tools # clang-format + protobuf-language-server ] - ++ lib.optionals pkgs.stdenv.isLinux [ traceroute ]; + ++ lib.optional pkgs.stdenv.isLinux [ traceroute ]; # Add entry to build a docker image with headscale # caveat: only works on Linux @@ -186,59 +212,6 @@ contents = [ pkgs.headscale ]; config.Entrypoint = [ (pkgs.headscale + "/bin/headscale") ]; }; - - # Go flake checks from the flake-checks library. CI gates on - # `nix build .#checks..`; the logic lives here, not in - # bespoke workflow steps. Linux-only: parts of the tree are - # Linux-specific and the pure unit subset is validated by CI. - fc = flake-checks.lib; - common = { - inherit pkgs; - root = ./.; - pname = "headscale"; - version = headscaleVersion; - vendorHash = (builtins.fromJSON (builtins.readFile ./flakehashes.json)).vendor.sri; - goPkg = pkgs.go_1_26; - # //go:embed targets and test-read files outside the default whitelist. - embedDirs = [ ./hscontrol/assets ./hscontrol/db/schema.sql ./config-example.yaml ]; - extraSrc = [ - ./hscontrol/testdata - ./hscontrol/types/testdata - ./hscontrol/db/testdata - ./hscontrol/policy/v2/testdata - ]; - }; - goChecks = { - build = fc.goBuild (common // { subPackages = [ "cmd/headscale" ]; }); - - # The pure unit subset. ./integration (Docker) and - # ./hscontrol/servertest (slow: 10s+ convergence plus race/stress/HA - # property tests — run by the servertest workflow instead) are dropped - # from the test set but kept in source so cmd/hi and friends still - # compile; TestPostgres* needs a server (the SQLite equivalents still - # run). CGO off matches the build. - gotest = fc.goTest (common // { - testExclude = [ "/integration" "/hscontrol/servertest" ]; - goSkip = [ "TestPostgres" ]; - testEnv = "export CGO_ENABLED=0"; - }); - - # Full-tree golangci-lint (golines, gofumpt, etc.); uses the overlay's - # golangci-lint built against the pinned Go. - golangci-lint = fc.goLint common; - - # nixpkgs-fmt + prettier, excluding generated output. goFmt = "off": - # Go formatting (golines, gofumpt) is enforced by the golangci-lint - # check, not treefmt. prettierExts matches the old prettier-lint glob - # (no json: testdata fixtures are hand-formatted). - formatting = fc.goFormat (common // { - goFmt = "off"; - prettier = true; - prettierExts = [ "ts" "js" "md" "yaml" "yml" "sass" "css" "scss" "html" ]; - # Mirror .prettierignore (docs/ are mkdocs-flavoured; gen/ generated). - fmtExclude = [ ./gen ./docs ]; - }); - }; in { # `nix develop` @@ -256,7 +229,7 @@ (pkgs.writeShellScriptBin "go-mod-update-all" '' - cat go.mod | ${pkgs.ripgrep}/bin/rg "\t" | ${pkgs.ripgrep}/bin/rg -v indirect | ${pkgs.gawk}/bin/awk '{print $1}' | ${pkgs.findutils}/bin/xargs go get -u + cat go.mod | ${pkgs.silver-searcher}/bin/ag "\t" | ${pkgs.silver-searcher}/bin/ag -v indirect | ${pkgs.gawk}/bin/awk '{print $1}' | ${pkgs.findutils}/bin/xargs go get -u go mod tidy '') ]; @@ -284,9 +257,6 @@ checks = { headscale = pkgs.testers.nixosTest (import ./nix/tests/headscale.nix); - } - # The Go build/test checks are gated to Linux: parts of the tree are - # Linux-specific and the pure unit subset is validated by CI. - // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux goChecks; + }; }); } diff --git a/flakehashes.json b/flakehashes.json index ea971a38e..5c88162e8 100644 --- a/flakehashes.json +++ b/flakehashes.json @@ -1,6 +1,6 @@ { "vendor": { - "goModSum": "sha256-SJml8RXGmb2p0g1nOsHn86FA1hwgd5ZffLSkUj5zek8=", - "sri": "sha256-pjGNuVtgFFzWNq/2cK7a4iyF13AfcHz098nk92a9Ido=" + "goModSum": "sha256-IE0n9cSqO4XNX4RN+CGBk9VC46iACiZKDFf/215iivk=", + "sri": "sha256-ijEIP9NSomhlWOgsVN7tPvSuvkTiLtnvXvhZmatIDLM=" } } diff --git a/gen/client/v1/client.gen.go b/gen/client/v1/client.gen.go deleted file mode 100644 index f2b120a46..000000000 --- a/gen/client/v1/client.gen.go +++ /dev/null @@ -1,4728 +0,0 @@ -// Package clientv1 provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.1 DO NOT EDIT. -package clientv1 - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" - - "github.com/oapi-codegen/runtime" -) - -const ( - BearerScopes bearerContextKey = "bearer.Scopes" -) - -// Defines values for NodeRegisterMethod. -const ( - REGISTERMETHODAUTHKEY NodeRegisterMethod = "REGISTER_METHOD_AUTH_KEY" - REGISTERMETHODCLI NodeRegisterMethod = "REGISTER_METHOD_CLI" - REGISTERMETHODOIDC NodeRegisterMethod = "REGISTER_METHOD_OIDC" - REGISTERMETHODUNSPECIFIED NodeRegisterMethod = "REGISTER_METHOD_UNSPECIFIED" -) - -// Valid indicates whether the value is a known member of the NodeRegisterMethod enum. -func (e NodeRegisterMethod) Valid() bool { - switch e { - case REGISTERMETHODAUTHKEY: - return true - case REGISTERMETHODCLI: - return true - case REGISTERMETHODOIDC: - return true - case REGISTERMETHODUNSPECIFIED: - return true - default: - return false - } -} - -// ApiKey defines model for ApiKey. -type ApiKey struct { - CreatedAt *time.Time `json:"createdAt"` - Expiration *time.Time `json:"expiration"` - Id string `json:"id"` - LastSeen *time.Time `json:"lastSeen"` - Prefix string `json:"prefix"` -} - -// AuthApproveOutputBody defines model for AuthApproveOutputBody. -type AuthApproveOutputBody = map[string]interface{} - -// AuthApproveRequestBody defines model for AuthApproveRequestBody. -type AuthApproveRequestBody struct { - AuthId *string `json:"authId,omitempty"` -} - -// AuthRegisterOutputBody defines model for AuthRegisterOutputBody. -type AuthRegisterOutputBody struct { - Node Node `json:"node"` -} - -// AuthRegisterRequestBody defines model for AuthRegisterRequestBody. -type AuthRegisterRequestBody struct { - AuthId *string `json:"authId,omitempty"` - User *string `json:"user,omitempty"` -} - -// AuthRejectOutputBody defines model for AuthRejectOutputBody. -type AuthRejectOutputBody = map[string]interface{} - -// AuthRejectRequestBody defines model for AuthRejectRequestBody. -type AuthRejectRequestBody struct { - AuthId *string `json:"authId,omitempty"` -} - -// BackfillNodeIPsOutputBody defines model for BackfillNodeIPsOutputBody. -type BackfillNodeIPsOutputBody struct { - Changes []string `json:"changes"` -} - -// CheckPolicyOutputBody defines model for CheckPolicyOutputBody. -type CheckPolicyOutputBody = map[string]interface{} - -// CreateApiKeyOutputBody defines model for CreateApiKeyOutputBody. -type CreateApiKeyOutputBody struct { - ApiKey string `json:"apiKey"` -} - -// CreateApiKeyRequestBody defines model for CreateApiKeyRequestBody. -type CreateApiKeyRequestBody struct { - Expiration *time.Time `json:"expiration,omitempty"` -} - -// CreatePreAuthKeyRequestBody defines model for CreatePreAuthKeyRequestBody. -type CreatePreAuthKeyRequestBody struct { - AclTags *[]string `json:"aclTags,omitempty"` - Ephemeral *bool `json:"ephemeral,omitempty"` - Expiration *time.Time `json:"expiration,omitempty"` - Reusable *bool `json:"reusable,omitempty"` - User *string `json:"user,omitempty"` -} - -// CreateUserRequestBody defines model for CreateUserRequestBody. -type CreateUserRequestBody struct { - DisplayName *string `json:"displayName,omitempty"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` - PictureUrl *string `json:"pictureUrl,omitempty"` -} - -// DebugCreateNodeRequestBody defines model for DebugCreateNodeRequestBody. -type DebugCreateNodeRequestBody struct { - Key *string `json:"key,omitempty"` - Name *string `json:"name,omitempty"` - Routes *[]string `json:"routes,omitempty"` - User *string `json:"user,omitempty"` -} - -// DeleteApiKeyOutputBody defines model for DeleteApiKeyOutputBody. -type DeleteApiKeyOutputBody = map[string]interface{} - -// DeleteNodeOutputBody defines model for DeleteNodeOutputBody. -type DeleteNodeOutputBody = map[string]interface{} - -// DeletePreAuthKeyOutputBody defines model for DeletePreAuthKeyOutputBody. -type DeletePreAuthKeyOutputBody = map[string]interface{} - -// DeleteUserOutputBody defines model for DeleteUserOutputBody. -type DeleteUserOutputBody = map[string]interface{} - -// ErrorDetail defines model for ErrorDetail. -type ErrorDetail struct { - // Location Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id' - Location *string `json:"location,omitempty"` - - // Message Error message text - Message *string `json:"message,omitempty"` - - // Value The value at the given location - Value interface{} `json:"value,omitempty"` -} - -// ErrorModel defines model for ErrorModel. -type ErrorModel struct { - // Detail A human-readable explanation specific to this occurrence of the problem. - Detail *string `json:"detail,omitempty"` - - // Errors Optional list of individual error details - Errors *[]ErrorDetail `json:"errors,omitempty"` - - // Instance A URI reference that identifies the specific occurrence of the problem. - Instance *string `json:"instance,omitempty"` - - // Status HTTP status code - Status *int64 `json:"status,omitempty"` - - // Title A short, human-readable summary of the problem type. This value should not change between occurrences of the error. - Title *string `json:"title,omitempty"` - - // Type A URI reference to human-readable documentation for the error. - Type *string `json:"type,omitempty"` -} - -// ExpireApiKeyOutputBody defines model for ExpireApiKeyOutputBody. -type ExpireApiKeyOutputBody = map[string]interface{} - -// ExpireApiKeyRequestBody defines model for ExpireApiKeyRequestBody. -type ExpireApiKeyRequestBody struct { - Id *string `json:"id,omitempty"` - Prefix *string `json:"prefix,omitempty"` -} - -// ExpireNodeRequestBody defines model for ExpireNodeRequestBody. -type ExpireNodeRequestBody struct { - DisableExpiry *bool `json:"disableExpiry,omitempty"` - Expiry *time.Time `json:"expiry,omitempty"` -} - -// ExpirePreAuthKeyOutputBody defines model for ExpirePreAuthKeyOutputBody. -type ExpirePreAuthKeyOutputBody = map[string]interface{} - -// ExpirePreAuthKeyRequestBody defines model for ExpirePreAuthKeyRequestBody. -type ExpirePreAuthKeyRequestBody struct { - Id *string `json:"id,omitempty"` -} - -// HealthResponseBody defines model for HealthResponseBody. -type HealthResponseBody struct { - DatabaseConnectivity bool `json:"databaseConnectivity"` -} - -// ListApiKeysOutputBody defines model for ListApiKeysOutputBody. -type ListApiKeysOutputBody struct { - ApiKeys []ApiKey `json:"apiKeys"` -} - -// ListNodesOutputBody defines model for ListNodesOutputBody. -type ListNodesOutputBody struct { - Nodes []Node `json:"nodes"` -} - -// ListPreAuthKeysOutputBody defines model for ListPreAuthKeysOutputBody. -type ListPreAuthKeysOutputBody struct { - PreAuthKeys []PreAuthKey `json:"preAuthKeys"` -} - -// ListUsersOutputBody defines model for ListUsersOutputBody. -type ListUsersOutputBody struct { - Users []User `json:"users"` -} - -// Node defines model for Node. -type Node struct { - ApprovedRoutes []string `json:"approvedRoutes"` - AvailableRoutes []string `json:"availableRoutes"` - CreatedAt time.Time `json:"createdAt"` - DiscoKey string `json:"discoKey"` - Expiry *time.Time `json:"expiry"` - GivenName string `json:"givenName"` - Id string `json:"id"` - IpAddresses []string `json:"ipAddresses"` - LastSeen *time.Time `json:"lastSeen"` - MachineKey string `json:"machineKey"` - Name string `json:"name"` - NodeKey string `json:"nodeKey"` - Online bool `json:"online"` - PreAuthKey NodePreAuthKey `json:"preAuthKey"` - RegisterMethod NodeRegisterMethod `json:"registerMethod"` - SubnetRoutes []string `json:"subnetRoutes"` - Tags []string `json:"tags"` - User User `json:"user"` -} - -// NodeRegisterMethod defines model for Node.RegisterMethod. -type NodeRegisterMethod string - -// NodeOutputBody defines model for NodeOutputBody. -type NodeOutputBody struct { - Node Node `json:"node"` -} - -// NodePreAuthKey defines model for NodePreAuthKey. -type NodePreAuthKey struct { - AclTags []string `json:"aclTags"` - CreatedAt *time.Time `json:"createdAt"` - Ephemeral bool `json:"ephemeral"` - Expiration *time.Time `json:"expiration"` - Id string `json:"id"` - Key string `json:"key"` - Reusable bool `json:"reusable"` - Used bool `json:"used"` - User User `json:"user"` -} - -// PolicyRequestBody defines model for PolicyRequestBody. -type PolicyRequestBody struct { - Policy *string `json:"policy,omitempty"` -} - -// PolicyResponseBody defines model for PolicyResponseBody. -type PolicyResponseBody struct { - Policy string `json:"policy"` - UpdatedAt time.Time `json:"updatedAt"` -} - -// PreAuthKey defines model for PreAuthKey. -type PreAuthKey struct { - AclTags []string `json:"aclTags"` - CreatedAt time.Time `json:"createdAt"` - Ephemeral bool `json:"ephemeral"` - Expiration time.Time `json:"expiration"` - Id string `json:"id"` - Key string `json:"key"` - Reusable bool `json:"reusable"` - Used bool `json:"used"` - User User `json:"user"` -} - -// PreAuthKeyOutputBody defines model for PreAuthKeyOutputBody. -type PreAuthKeyOutputBody struct { - PreAuthKey PreAuthKey `json:"preAuthKey"` -} - -// SetApprovedRoutesRequestBody defines model for SetApprovedRoutesRequestBody. -type SetApprovedRoutesRequestBody struct { - Routes *[]string `json:"routes,omitempty"` -} - -// SetTagsRequestBody defines model for SetTagsRequestBody. -type SetTagsRequestBody struct { - Tags *[]string `json:"tags,omitempty"` -} - -// User defines model for User. -type User struct { - CreatedAt time.Time `json:"createdAt"` - DisplayName string `json:"displayName"` - Email string `json:"email"` - Id string `json:"id"` - Name string `json:"name"` - ProfilePicUrl string `json:"profilePicUrl"` - Provider string `json:"provider"` - ProviderId string `json:"providerId"` -} - -// UserOutputBody defines model for UserOutputBody. -type UserOutputBody struct { - User User `json:"user"` -} - -// bearerContextKey is the context key for bearer security scheme -type bearerContextKey string - -// DeleteApiKeyParams defines parameters for DeleteApiKey. -type DeleteApiKeyParams struct { - Id *string `form:"id,omitempty" json:"id,omitempty"` -} - -// ListNodesParams defines parameters for ListNodes. -type ListNodesParams struct { - User *string `form:"user,omitempty" json:"user,omitempty"` -} - -// BackfillNodeIPsParams defines parameters for BackfillNodeIPs. -type BackfillNodeIPsParams struct { - Confirmed *bool `form:"confirmed,omitempty" json:"confirmed,omitempty"` -} - -// RegisterNodeParams defines parameters for RegisterNode. -type RegisterNodeParams struct { - User *string `form:"user,omitempty" json:"user,omitempty"` - Key *string `form:"key,omitempty" json:"key,omitempty"` -} - -// DeletePreAuthKeyParams defines parameters for DeletePreAuthKey. -type DeletePreAuthKeyParams struct { - Id *string `form:"id,omitempty" json:"id,omitempty"` -} - -// ListUsersParams defines parameters for ListUsers. -type ListUsersParams struct { - Id *string `form:"id,omitempty" json:"id,omitempty"` - Name *string `form:"name,omitempty" json:"name,omitempty"` - Email *string `form:"email,omitempty" json:"email,omitempty"` -} - -// CreateApiKeyJSONRequestBody defines body for CreateApiKey for application/json ContentType. -type CreateApiKeyJSONRequestBody = CreateApiKeyRequestBody - -// ExpireApiKeyJSONRequestBody defines body for ExpireApiKey for application/json ContentType. -type ExpireApiKeyJSONRequestBody = ExpireApiKeyRequestBody - -// AuthApproveJSONRequestBody defines body for AuthApprove for application/json ContentType. -type AuthApproveJSONRequestBody = AuthApproveRequestBody - -// AuthRegisterJSONRequestBody defines body for AuthRegister for application/json ContentType. -type AuthRegisterJSONRequestBody = AuthRegisterRequestBody - -// AuthRejectJSONRequestBody defines body for AuthReject for application/json ContentType. -type AuthRejectJSONRequestBody = AuthRejectRequestBody - -// DebugCreateNodeJSONRequestBody defines body for DebugCreateNode for application/json ContentType. -type DebugCreateNodeJSONRequestBody = DebugCreateNodeRequestBody - -// SetApprovedRoutesJSONRequestBody defines body for SetApprovedRoutes for application/json ContentType. -type SetApprovedRoutesJSONRequestBody = SetApprovedRoutesRequestBody - -// ExpireNodeJSONRequestBody defines body for ExpireNode for application/json ContentType. -type ExpireNodeJSONRequestBody = ExpireNodeRequestBody - -// SetTagsJSONRequestBody defines body for SetTags for application/json ContentType. -type SetTagsJSONRequestBody = SetTagsRequestBody - -// SetPolicyJSONRequestBody defines body for SetPolicy for application/json ContentType. -type SetPolicyJSONRequestBody = PolicyRequestBody - -// CheckPolicyJSONRequestBody defines body for CheckPolicy for application/json ContentType. -type CheckPolicyJSONRequestBody = PolicyRequestBody - -// CreatePreAuthKeyJSONRequestBody defines body for CreatePreAuthKey for application/json ContentType. -type CreatePreAuthKeyJSONRequestBody = CreatePreAuthKeyRequestBody - -// ExpirePreAuthKeyJSONRequestBody defines body for ExpirePreAuthKey for application/json ContentType. -type ExpirePreAuthKeyJSONRequestBody = ExpirePreAuthKeyRequestBody - -// CreateUserJSONRequestBody defines body for CreateUser for application/json ContentType. -type CreateUserJSONRequestBody = CreateUserRequestBody - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer - - // A list of callbacks for modifying requests which are generated before sending over - // the network. - RequestEditors []RequestEditorFn -} - -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, - } - // mutate client and add all optional params - for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" - } - // create httpClient, if not already present - if client.Client == nil { - client.Client = &http.Client{} - } - return &client, nil -} - -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} - -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditors = append(c.RequestEditors, fn) - return nil - } -} - -// The interface specification for the client above. -type ClientInterface interface { - // ListApiKeys request - ListApiKeys(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateApiKeyWithBody request with any body - CreateApiKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateApiKey(ctx context.Context, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ExpireApiKeyWithBody request with any body - ExpireApiKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - ExpireApiKey(ctx context.Context, body ExpireApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeleteApiKey request - DeleteApiKey(ctx context.Context, prefix string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // AuthApproveWithBody request with any body - AuthApproveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - AuthApprove(ctx context.Context, body AuthApproveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // AuthRegisterWithBody request with any body - AuthRegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - AuthRegister(ctx context.Context, body AuthRegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // AuthRejectWithBody request with any body - AuthRejectWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - AuthReject(ctx context.Context, body AuthRejectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DebugCreateNodeWithBody request with any body - DebugCreateNodeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - DebugCreateNode(ctx context.Context, body DebugCreateNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // Health request - Health(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListNodes request - ListNodes(ctx context.Context, params *ListNodesParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // BackfillNodeIPs request - BackfillNodeIPs(ctx context.Context, params *BackfillNodeIPsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RegisterNode request - RegisterNode(ctx context.Context, params *RegisterNodeParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeleteNode request - DeleteNode(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetNode request - GetNode(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetApprovedRoutesWithBody request with any body - SetApprovedRoutesWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetApprovedRoutes(ctx context.Context, nodeId string, body SetApprovedRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ExpireNodeWithBody request with any body - ExpireNodeWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - ExpireNode(ctx context.Context, nodeId string, body ExpireNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RenameNode request - RenameNode(ctx context.Context, nodeId string, newName string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetTagsWithBody request with any body - SetTagsWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetTags(ctx context.Context, nodeId string, body SetTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetPolicy request - GetPolicy(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetPolicyWithBody request with any body - SetPolicyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetPolicy(ctx context.Context, body SetPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CheckPolicyWithBody request with any body - CheckPolicyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CheckPolicy(ctx context.Context, body CheckPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeletePreAuthKey request - DeletePreAuthKey(ctx context.Context, params *DeletePreAuthKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListPreAuthKeys request - ListPreAuthKeys(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreatePreAuthKeyWithBody request with any body - CreatePreAuthKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreatePreAuthKey(ctx context.Context, body CreatePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ExpirePreAuthKeyWithBody request with any body - ExpirePreAuthKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - ExpirePreAuthKey(ctx context.Context, body ExpirePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListUsers request - ListUsers(ctx context.Context, params *ListUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateUserWithBody request with any body - CreateUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateUser(ctx context.Context, body CreateUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeleteUser request - DeleteUser(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RenameUser request - RenameUser(ctx context.Context, oldId string, newName string, reqEditors ...RequestEditorFn) (*http.Response, error) -} - -func (c *Client) ListApiKeys(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListApiKeysRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateApiKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateApiKeyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateApiKey(ctx context.Context, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateApiKeyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpireApiKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpireApiKeyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpireApiKey(ctx context.Context, body ExpireApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpireApiKeyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DeleteApiKey(ctx context.Context, prefix string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteApiKeyRequest(c.Server, prefix, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthApproveWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthApproveRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthApprove(ctx context.Context, body AuthApproveJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthApproveRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthRegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthRegisterRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthRegister(ctx context.Context, body AuthRegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthRegisterRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthRejectWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthRejectRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthReject(ctx context.Context, body AuthRejectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthRejectRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DebugCreateNodeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDebugCreateNodeRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DebugCreateNode(ctx context.Context, body DebugCreateNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDebugCreateNodeRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) Health(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHealthRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListNodes(ctx context.Context, params *ListNodesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListNodesRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) BackfillNodeIPs(ctx context.Context, params *BackfillNodeIPsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewBackfillNodeIPsRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RegisterNode(ctx context.Context, params *RegisterNodeParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRegisterNodeRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DeleteNode(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteNodeRequest(c.Server, nodeId) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetNode(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetNodeRequest(c.Server, nodeId) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetApprovedRoutesWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetApprovedRoutesRequestWithBody(c.Server, nodeId, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetApprovedRoutes(ctx context.Context, nodeId string, body SetApprovedRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetApprovedRoutesRequest(c.Server, nodeId, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpireNodeWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpireNodeRequestWithBody(c.Server, nodeId, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpireNode(ctx context.Context, nodeId string, body ExpireNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpireNodeRequest(c.Server, nodeId, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RenameNode(ctx context.Context, nodeId string, newName string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRenameNodeRequest(c.Server, nodeId, newName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetTagsWithBody(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetTagsRequestWithBody(c.Server, nodeId, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetTags(ctx context.Context, nodeId string, body SetTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetTagsRequest(c.Server, nodeId, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetPolicy(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetPolicyRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetPolicyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetPolicyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetPolicy(ctx context.Context, body SetPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetPolicyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CheckPolicyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCheckPolicyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CheckPolicy(ctx context.Context, body CheckPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCheckPolicyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DeletePreAuthKey(ctx context.Context, params *DeletePreAuthKeyParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeletePreAuthKeyRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListPreAuthKeys(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListPreAuthKeysRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreatePreAuthKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreatePreAuthKeyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreatePreAuthKey(ctx context.Context, body CreatePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreatePreAuthKeyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpirePreAuthKeyWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpirePreAuthKeyRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ExpirePreAuthKey(ctx context.Context, body ExpirePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewExpirePreAuthKeyRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListUsers(ctx context.Context, params *ListUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListUsersRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateUserRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateUser(ctx context.Context, body CreateUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateUserRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DeleteUser(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteUserRequest(c.Server, id) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RenameUser(ctx context.Context, oldId string, newName string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRenameUserRequest(c.Server, oldId, newName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -// NewListApiKeysRequest generates requests for ListApiKeys -func NewListApiKeysRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/apikey") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreateApiKeyRequest calls the generic CreateApiKey builder with application/json body -func NewCreateApiKeyRequest(server string, body CreateApiKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateApiKeyRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreateApiKeyRequestWithBody generates requests for CreateApiKey with any type of body -func NewCreateApiKeyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/apikey") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewExpireApiKeyRequest calls the generic ExpireApiKey builder with application/json body -func NewExpireApiKeyRequest(server string, body ExpireApiKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewExpireApiKeyRequestWithBody(server, "application/json", bodyReader) -} - -// NewExpireApiKeyRequestWithBody generates requests for ExpireApiKey with any type of body -func NewExpireApiKeyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/apikey/expire") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteApiKeyRequest generates requests for DeleteApiKey -func NewDeleteApiKeyRequest(server string, prefix string, params *DeleteApiKeyParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "prefix", prefix, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/apikey/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "id", *params.Id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "uint64"}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewAuthApproveRequest calls the generic AuthApprove builder with application/json body -func NewAuthApproveRequest(server string, body AuthApproveJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewAuthApproveRequestWithBody(server, "application/json", bodyReader) -} - -// NewAuthApproveRequestWithBody generates requests for AuthApprove with any type of body -func NewAuthApproveRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/auth/approve") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewAuthRegisterRequest calls the generic AuthRegister builder with application/json body -func NewAuthRegisterRequest(server string, body AuthRegisterJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewAuthRegisterRequestWithBody(server, "application/json", bodyReader) -} - -// NewAuthRegisterRequestWithBody generates requests for AuthRegister with any type of body -func NewAuthRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/auth/register") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewAuthRejectRequest calls the generic AuthReject builder with application/json body -func NewAuthRejectRequest(server string, body AuthRejectJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewAuthRejectRequestWithBody(server, "application/json", bodyReader) -} - -// NewAuthRejectRequestWithBody generates requests for AuthReject with any type of body -func NewAuthRejectRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/auth/reject") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDebugCreateNodeRequest calls the generic DebugCreateNode builder with application/json body -func NewDebugCreateNodeRequest(server string, body DebugCreateNodeJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDebugCreateNodeRequestWithBody(server, "application/json", bodyReader) -} - -// NewDebugCreateNodeRequestWithBody generates requests for DebugCreateNode with any type of body -func NewDebugCreateNodeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/debug/node") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewHealthRequest generates requests for Health -func NewHealthRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/health") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewListNodesRequest generates requests for ListNodes -func NewListNodesRequest(server string, params *ListNodesParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.User != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "user", *params.User, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewBackfillNodeIPsRequest generates requests for BackfillNodeIPs -func NewBackfillNodeIPsRequest(server string, params *BackfillNodeIPsParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/backfillips") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Confirmed != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "confirmed", *params.Confirmed, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "boolean", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewRegisterNodeRequest generates requests for RegisterNode -func NewRegisterNodeRequest(server string, params *RegisterNodeParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/register") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.User != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "user", *params.User, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Key != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "key", *params.Key, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewDeleteNodeRequest generates requests for DeleteNode -func NewDeleteNodeRequest(server string, nodeId string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetNodeRequest generates requests for GetNode -func NewGetNodeRequest(server string, nodeId string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewSetApprovedRoutesRequest calls the generic SetApprovedRoutes builder with application/json body -func NewSetApprovedRoutesRequest(server string, nodeId string, body SetApprovedRoutesJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetApprovedRoutesRequestWithBody(server, nodeId, "application/json", bodyReader) -} - -// NewSetApprovedRoutesRequestWithBody generates requests for SetApprovedRoutes with any type of body -func NewSetApprovedRoutesRequestWithBody(server string, nodeId string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s/approve_routes", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewExpireNodeRequest calls the generic ExpireNode builder with application/json body -func NewExpireNodeRequest(server string, nodeId string, body ExpireNodeJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewExpireNodeRequestWithBody(server, nodeId, "application/json", bodyReader) -} - -// NewExpireNodeRequestWithBody generates requests for ExpireNode with any type of body -func NewExpireNodeRequestWithBody(server string, nodeId string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s/expire", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewRenameNodeRequest generates requests for RenameNode -func NewRenameNodeRequest(server string, nodeId string, newName string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "newName", newName, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s/rename/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewSetTagsRequest calls the generic SetTags builder with application/json body -func NewSetTagsRequest(server string, nodeId string, body SetTagsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetTagsRequestWithBody(server, nodeId, "application/json", bodyReader) -} - -// NewSetTagsRequestWithBody generates requests for SetTags with any type of body -func NewSetTagsRequestWithBody(server string, nodeId string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "nodeId", nodeId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/node/%s/tags", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewGetPolicyRequest generates requests for GetPolicy -func NewGetPolicyRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/policy") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewSetPolicyRequest calls the generic SetPolicy builder with application/json body -func NewSetPolicyRequest(server string, body SetPolicyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetPolicyRequestWithBody(server, "application/json", bodyReader) -} - -// NewSetPolicyRequestWithBody generates requests for SetPolicy with any type of body -func NewSetPolicyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/policy") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPut, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewCheckPolicyRequest calls the generic CheckPolicy builder with application/json body -func NewCheckPolicyRequest(server string, body CheckPolicyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCheckPolicyRequestWithBody(server, "application/json", bodyReader) -} - -// NewCheckPolicyRequestWithBody generates requests for CheckPolicy with any type of body -func NewCheckPolicyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/policy/check") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeletePreAuthKeyRequest generates requests for DeletePreAuthKey -func NewDeletePreAuthKeyRequest(server string, params *DeletePreAuthKeyParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/preauthkey") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "id", *params.Id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "uint64"}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewListPreAuthKeysRequest generates requests for ListPreAuthKeys -func NewListPreAuthKeysRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/preauthkey") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreatePreAuthKeyRequest calls the generic CreatePreAuthKey builder with application/json body -func NewCreatePreAuthKeyRequest(server string, body CreatePreAuthKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreatePreAuthKeyRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreatePreAuthKeyRequestWithBody generates requests for CreatePreAuthKey with any type of body -func NewCreatePreAuthKeyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/preauthkey") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewExpirePreAuthKeyRequest calls the generic ExpirePreAuthKey builder with application/json body -func NewExpirePreAuthKeyRequest(server string, body ExpirePreAuthKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewExpirePreAuthKeyRequestWithBody(server, "application/json", bodyReader) -} - -// NewExpirePreAuthKeyRequestWithBody generates requests for ExpirePreAuthKey with any type of body -func NewExpirePreAuthKeyRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/preauthkey/expire") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewListUsersRequest generates requests for ListUsers -func NewListUsersRequest(server string, params *ListUsersParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/user") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "id", *params.Id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "uint64"}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Name != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "name", *params.Name, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Email != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "email", *params.Email, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreateUserRequest calls the generic CreateUser builder with application/json body -func NewCreateUserRequest(server string, body CreateUserJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateUserRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreateUserRequestWithBody generates requests for CreateUser with any type of body -func NewCreateUserRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/user") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteUserRequest generates requests for DeleteUser -func NewDeleteUserRequest(server string, id string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/user/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewRenameUserRequest generates requests for RenameUser -func NewRenameUserRequest(server string, oldId string, newName string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "oldId", oldId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: "uint64"}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "newName", newName, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/user/%s/rename/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // ListApiKeysWithResponse request - ListApiKeysWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListApiKeysResponse, error) - - // CreateApiKeyWithBodyWithResponse request with any body - CreateApiKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) - - CreateApiKeyWithResponse(ctx context.Context, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) - - // ExpireApiKeyWithBodyWithResponse request with any body - ExpireApiKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpireApiKeyResponse, error) - - ExpireApiKeyWithResponse(ctx context.Context, body ExpireApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpireApiKeyResponse, error) - - // DeleteApiKeyWithResponse request - DeleteApiKeyWithResponse(ctx context.Context, prefix string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) - - // AuthApproveWithBodyWithResponse request with any body - AuthApproveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthApproveResponse, error) - - AuthApproveWithResponse(ctx context.Context, body AuthApproveJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthApproveResponse, error) - - // AuthRegisterWithBodyWithResponse request with any body - AuthRegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthRegisterResponse, error) - - AuthRegisterWithResponse(ctx context.Context, body AuthRegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthRegisterResponse, error) - - // AuthRejectWithBodyWithResponse request with any body - AuthRejectWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthRejectResponse, error) - - AuthRejectWithResponse(ctx context.Context, body AuthRejectJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthRejectResponse, error) - - // DebugCreateNodeWithBodyWithResponse request with any body - DebugCreateNodeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DebugCreateNodeResponse, error) - - DebugCreateNodeWithResponse(ctx context.Context, body DebugCreateNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*DebugCreateNodeResponse, error) - - // HealthWithResponse request - HealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*HealthResponse, error) - - // ListNodesWithResponse request - ListNodesWithResponse(ctx context.Context, params *ListNodesParams, reqEditors ...RequestEditorFn) (*ListNodesResponse, error) - - // BackfillNodeIPsWithResponse request - BackfillNodeIPsWithResponse(ctx context.Context, params *BackfillNodeIPsParams, reqEditors ...RequestEditorFn) (*BackfillNodeIPsResponse, error) - - // RegisterNodeWithResponse request - RegisterNodeWithResponse(ctx context.Context, params *RegisterNodeParams, reqEditors ...RequestEditorFn) (*RegisterNodeResponse, error) - - // DeleteNodeWithResponse request - DeleteNodeWithResponse(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*DeleteNodeResponse, error) - - // GetNodeWithResponse request - GetNodeWithResponse(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*GetNodeResponse, error) - - // SetApprovedRoutesWithBodyWithResponse request with any body - SetApprovedRoutesWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetApprovedRoutesResponse, error) - - SetApprovedRoutesWithResponse(ctx context.Context, nodeId string, body SetApprovedRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*SetApprovedRoutesResponse, error) - - // ExpireNodeWithBodyWithResponse request with any body - ExpireNodeWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpireNodeResponse, error) - - ExpireNodeWithResponse(ctx context.Context, nodeId string, body ExpireNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpireNodeResponse, error) - - // RenameNodeWithResponse request - RenameNodeWithResponse(ctx context.Context, nodeId string, newName string, reqEditors ...RequestEditorFn) (*RenameNodeResponse, error) - - // SetTagsWithBodyWithResponse request with any body - SetTagsWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetTagsResponse, error) - - SetTagsWithResponse(ctx context.Context, nodeId string, body SetTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetTagsResponse, error) - - // GetPolicyWithResponse request - GetPolicyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPolicyResponse, error) - - // SetPolicyWithBodyWithResponse request with any body - SetPolicyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetPolicyResponse, error) - - SetPolicyWithResponse(ctx context.Context, body SetPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*SetPolicyResponse, error) - - // CheckPolicyWithBodyWithResponse request with any body - CheckPolicyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CheckPolicyResponse, error) - - CheckPolicyWithResponse(ctx context.Context, body CheckPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*CheckPolicyResponse, error) - - // DeletePreAuthKeyWithResponse request - DeletePreAuthKeyWithResponse(ctx context.Context, params *DeletePreAuthKeyParams, reqEditors ...RequestEditorFn) (*DeletePreAuthKeyResponse, error) - - // ListPreAuthKeysWithResponse request - ListPreAuthKeysWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListPreAuthKeysResponse, error) - - // CreatePreAuthKeyWithBodyWithResponse request with any body - CreatePreAuthKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreatePreAuthKeyResponse, error) - - CreatePreAuthKeyWithResponse(ctx context.Context, body CreatePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreatePreAuthKeyResponse, error) - - // ExpirePreAuthKeyWithBodyWithResponse request with any body - ExpirePreAuthKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpirePreAuthKeyResponse, error) - - ExpirePreAuthKeyWithResponse(ctx context.Context, body ExpirePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpirePreAuthKeyResponse, error) - - // ListUsersWithResponse request - ListUsersWithResponse(ctx context.Context, params *ListUsersParams, reqEditors ...RequestEditorFn) (*ListUsersResponse, error) - - // CreateUserWithBodyWithResponse request with any body - CreateUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateUserResponse, error) - - CreateUserWithResponse(ctx context.Context, body CreateUserJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateUserResponse, error) - - // DeleteUserWithResponse request - DeleteUserWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteUserResponse, error) - - // RenameUserWithResponse request - RenameUserWithResponse(ctx context.Context, oldId string, newName string, reqEditors ...RequestEditorFn) (*RenameUserResponse, error) -} - -type ListApiKeysResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListApiKeysOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListApiKeysResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListApiKeysResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListApiKeysResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CreateApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *CreateApiKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r CreateApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CreateApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ExpireApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ExpireApiKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ExpireApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ExpireApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ExpireApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DeleteApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeleteApiKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeleteApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type AuthApproveResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthApproveOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r AuthApproveResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r AuthApproveResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AuthApproveResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type AuthRegisterResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthRegisterOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r AuthRegisterResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r AuthRegisterResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AuthRegisterResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type AuthRejectResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *AuthRejectOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r AuthRejectResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r AuthRejectResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AuthRejectResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DebugCreateNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DebugCreateNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DebugCreateNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DebugCreateNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type HealthResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *HealthResponseBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r HealthResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r HealthResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r HealthResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListNodesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListNodesOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListNodesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListNodesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListNodesResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type BackfillNodeIPsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *BackfillNodeIPsOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r BackfillNodeIPsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r BackfillNodeIPsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r BackfillNodeIPsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type RegisterNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r RegisterNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r RegisterNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RegisterNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DeleteNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeleteNodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeleteNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetApprovedRoutesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetApprovedRoutesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetApprovedRoutesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetApprovedRoutesResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ExpireNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ExpireNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ExpireNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ExpireNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type RenameNodeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r RenameNodeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r RenameNodeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RenameNodeResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetTagsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *NodeOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetTagsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetTagsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetTagsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetPolicyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *PolicyResponseBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetPolicyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetPolicyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetPolicyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetPolicyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *PolicyResponseBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetPolicyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetPolicyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetPolicyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CheckPolicyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *CheckPolicyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r CheckPolicyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CheckPolicyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CheckPolicyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DeletePreAuthKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeletePreAuthKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeletePreAuthKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeletePreAuthKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeletePreAuthKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListPreAuthKeysResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListPreAuthKeysOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListPreAuthKeysResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListPreAuthKeysResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListPreAuthKeysResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CreatePreAuthKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *PreAuthKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r CreatePreAuthKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CreatePreAuthKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreatePreAuthKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ExpirePreAuthKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ExpirePreAuthKeyOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ExpirePreAuthKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ExpirePreAuthKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ExpirePreAuthKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListUsersResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListUsersOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListUsersResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListUsersResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListUsersResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CreateUserResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *UserOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r CreateUserResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CreateUserResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateUserResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DeleteUserResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeleteUserOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeleteUserResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteUserResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteUserResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type RenameUserResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *UserOutputBody - ApplicationproblemJSONDefault *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r RenameUserResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r RenameUserResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RenameUserResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -// ListApiKeysWithResponse request returning *ListApiKeysResponse -func (c *ClientWithResponses) ListApiKeysWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListApiKeysResponse, error) { - rsp, err := c.ListApiKeys(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseListApiKeysResponse(rsp) -} - -// CreateApiKeyWithBodyWithResponse request with arbitrary body returning *CreateApiKeyResponse -func (c *ClientWithResponses) CreateApiKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { - rsp, err := c.CreateApiKeyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateApiKeyResponse(rsp) -} - -func (c *ClientWithResponses) CreateApiKeyWithResponse(ctx context.Context, body CreateApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateApiKeyResponse, error) { - rsp, err := c.CreateApiKey(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateApiKeyResponse(rsp) -} - -// ExpireApiKeyWithBodyWithResponse request with arbitrary body returning *ExpireApiKeyResponse -func (c *ClientWithResponses) ExpireApiKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpireApiKeyResponse, error) { - rsp, err := c.ExpireApiKeyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpireApiKeyResponse(rsp) -} - -func (c *ClientWithResponses) ExpireApiKeyWithResponse(ctx context.Context, body ExpireApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpireApiKeyResponse, error) { - rsp, err := c.ExpireApiKey(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpireApiKeyResponse(rsp) -} - -// DeleteApiKeyWithResponse request returning *DeleteApiKeyResponse -func (c *ClientWithResponses) DeleteApiKeyWithResponse(ctx context.Context, prefix string, params *DeleteApiKeyParams, reqEditors ...RequestEditorFn) (*DeleteApiKeyResponse, error) { - rsp, err := c.DeleteApiKey(ctx, prefix, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteApiKeyResponse(rsp) -} - -// AuthApproveWithBodyWithResponse request with arbitrary body returning *AuthApproveResponse -func (c *ClientWithResponses) AuthApproveWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthApproveResponse, error) { - rsp, err := c.AuthApproveWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthApproveResponse(rsp) -} - -func (c *ClientWithResponses) AuthApproveWithResponse(ctx context.Context, body AuthApproveJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthApproveResponse, error) { - rsp, err := c.AuthApprove(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthApproveResponse(rsp) -} - -// AuthRegisterWithBodyWithResponse request with arbitrary body returning *AuthRegisterResponse -func (c *ClientWithResponses) AuthRegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthRegisterResponse, error) { - rsp, err := c.AuthRegisterWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthRegisterResponse(rsp) -} - -func (c *ClientWithResponses) AuthRegisterWithResponse(ctx context.Context, body AuthRegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthRegisterResponse, error) { - rsp, err := c.AuthRegister(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthRegisterResponse(rsp) -} - -// AuthRejectWithBodyWithResponse request with arbitrary body returning *AuthRejectResponse -func (c *ClientWithResponses) AuthRejectWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthRejectResponse, error) { - rsp, err := c.AuthRejectWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthRejectResponse(rsp) -} - -func (c *ClientWithResponses) AuthRejectWithResponse(ctx context.Context, body AuthRejectJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthRejectResponse, error) { - rsp, err := c.AuthReject(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthRejectResponse(rsp) -} - -// DebugCreateNodeWithBodyWithResponse request with arbitrary body returning *DebugCreateNodeResponse -func (c *ClientWithResponses) DebugCreateNodeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DebugCreateNodeResponse, error) { - rsp, err := c.DebugCreateNodeWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseDebugCreateNodeResponse(rsp) -} - -func (c *ClientWithResponses) DebugCreateNodeWithResponse(ctx context.Context, body DebugCreateNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*DebugCreateNodeResponse, error) { - rsp, err := c.DebugCreateNode(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseDebugCreateNodeResponse(rsp) -} - -// HealthWithResponse request returning *HealthResponse -func (c *ClientWithResponses) HealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*HealthResponse, error) { - rsp, err := c.Health(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseHealthResponse(rsp) -} - -// ListNodesWithResponse request returning *ListNodesResponse -func (c *ClientWithResponses) ListNodesWithResponse(ctx context.Context, params *ListNodesParams, reqEditors ...RequestEditorFn) (*ListNodesResponse, error) { - rsp, err := c.ListNodes(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListNodesResponse(rsp) -} - -// BackfillNodeIPsWithResponse request returning *BackfillNodeIPsResponse -func (c *ClientWithResponses) BackfillNodeIPsWithResponse(ctx context.Context, params *BackfillNodeIPsParams, reqEditors ...RequestEditorFn) (*BackfillNodeIPsResponse, error) { - rsp, err := c.BackfillNodeIPs(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseBackfillNodeIPsResponse(rsp) -} - -// RegisterNodeWithResponse request returning *RegisterNodeResponse -func (c *ClientWithResponses) RegisterNodeWithResponse(ctx context.Context, params *RegisterNodeParams, reqEditors ...RequestEditorFn) (*RegisterNodeResponse, error) { - rsp, err := c.RegisterNode(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseRegisterNodeResponse(rsp) -} - -// DeleteNodeWithResponse request returning *DeleteNodeResponse -func (c *ClientWithResponses) DeleteNodeWithResponse(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*DeleteNodeResponse, error) { - rsp, err := c.DeleteNode(ctx, nodeId, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteNodeResponse(rsp) -} - -// GetNodeWithResponse request returning *GetNodeResponse -func (c *ClientWithResponses) GetNodeWithResponse(ctx context.Context, nodeId string, reqEditors ...RequestEditorFn) (*GetNodeResponse, error) { - rsp, err := c.GetNode(ctx, nodeId, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetNodeResponse(rsp) -} - -// SetApprovedRoutesWithBodyWithResponse request with arbitrary body returning *SetApprovedRoutesResponse -func (c *ClientWithResponses) SetApprovedRoutesWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetApprovedRoutesResponse, error) { - rsp, err := c.SetApprovedRoutesWithBody(ctx, nodeId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetApprovedRoutesResponse(rsp) -} - -func (c *ClientWithResponses) SetApprovedRoutesWithResponse(ctx context.Context, nodeId string, body SetApprovedRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*SetApprovedRoutesResponse, error) { - rsp, err := c.SetApprovedRoutes(ctx, nodeId, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetApprovedRoutesResponse(rsp) -} - -// ExpireNodeWithBodyWithResponse request with arbitrary body returning *ExpireNodeResponse -func (c *ClientWithResponses) ExpireNodeWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpireNodeResponse, error) { - rsp, err := c.ExpireNodeWithBody(ctx, nodeId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpireNodeResponse(rsp) -} - -func (c *ClientWithResponses) ExpireNodeWithResponse(ctx context.Context, nodeId string, body ExpireNodeJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpireNodeResponse, error) { - rsp, err := c.ExpireNode(ctx, nodeId, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpireNodeResponse(rsp) -} - -// RenameNodeWithResponse request returning *RenameNodeResponse -func (c *ClientWithResponses) RenameNodeWithResponse(ctx context.Context, nodeId string, newName string, reqEditors ...RequestEditorFn) (*RenameNodeResponse, error) { - rsp, err := c.RenameNode(ctx, nodeId, newName, reqEditors...) - if err != nil { - return nil, err - } - return ParseRenameNodeResponse(rsp) -} - -// SetTagsWithBodyWithResponse request with arbitrary body returning *SetTagsResponse -func (c *ClientWithResponses) SetTagsWithBodyWithResponse(ctx context.Context, nodeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetTagsResponse, error) { - rsp, err := c.SetTagsWithBody(ctx, nodeId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetTagsResponse(rsp) -} - -func (c *ClientWithResponses) SetTagsWithResponse(ctx context.Context, nodeId string, body SetTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetTagsResponse, error) { - rsp, err := c.SetTags(ctx, nodeId, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetTagsResponse(rsp) -} - -// GetPolicyWithResponse request returning *GetPolicyResponse -func (c *ClientWithResponses) GetPolicyWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetPolicyResponse, error) { - rsp, err := c.GetPolicy(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetPolicyResponse(rsp) -} - -// SetPolicyWithBodyWithResponse request with arbitrary body returning *SetPolicyResponse -func (c *ClientWithResponses) SetPolicyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetPolicyResponse, error) { - rsp, err := c.SetPolicyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetPolicyResponse(rsp) -} - -func (c *ClientWithResponses) SetPolicyWithResponse(ctx context.Context, body SetPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*SetPolicyResponse, error) { - rsp, err := c.SetPolicy(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetPolicyResponse(rsp) -} - -// CheckPolicyWithBodyWithResponse request with arbitrary body returning *CheckPolicyResponse -func (c *ClientWithResponses) CheckPolicyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CheckPolicyResponse, error) { - rsp, err := c.CheckPolicyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCheckPolicyResponse(rsp) -} - -func (c *ClientWithResponses) CheckPolicyWithResponse(ctx context.Context, body CheckPolicyJSONRequestBody, reqEditors ...RequestEditorFn) (*CheckPolicyResponse, error) { - rsp, err := c.CheckPolicy(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCheckPolicyResponse(rsp) -} - -// DeletePreAuthKeyWithResponse request returning *DeletePreAuthKeyResponse -func (c *ClientWithResponses) DeletePreAuthKeyWithResponse(ctx context.Context, params *DeletePreAuthKeyParams, reqEditors ...RequestEditorFn) (*DeletePreAuthKeyResponse, error) { - rsp, err := c.DeletePreAuthKey(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeletePreAuthKeyResponse(rsp) -} - -// ListPreAuthKeysWithResponse request returning *ListPreAuthKeysResponse -func (c *ClientWithResponses) ListPreAuthKeysWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListPreAuthKeysResponse, error) { - rsp, err := c.ListPreAuthKeys(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseListPreAuthKeysResponse(rsp) -} - -// CreatePreAuthKeyWithBodyWithResponse request with arbitrary body returning *CreatePreAuthKeyResponse -func (c *ClientWithResponses) CreatePreAuthKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreatePreAuthKeyResponse, error) { - rsp, err := c.CreatePreAuthKeyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreatePreAuthKeyResponse(rsp) -} - -func (c *ClientWithResponses) CreatePreAuthKeyWithResponse(ctx context.Context, body CreatePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreatePreAuthKeyResponse, error) { - rsp, err := c.CreatePreAuthKey(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreatePreAuthKeyResponse(rsp) -} - -// ExpirePreAuthKeyWithBodyWithResponse request with arbitrary body returning *ExpirePreAuthKeyResponse -func (c *ClientWithResponses) ExpirePreAuthKeyWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExpirePreAuthKeyResponse, error) { - rsp, err := c.ExpirePreAuthKeyWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpirePreAuthKeyResponse(rsp) -} - -func (c *ClientWithResponses) ExpirePreAuthKeyWithResponse(ctx context.Context, body ExpirePreAuthKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*ExpirePreAuthKeyResponse, error) { - rsp, err := c.ExpirePreAuthKey(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseExpirePreAuthKeyResponse(rsp) -} - -// ListUsersWithResponse request returning *ListUsersResponse -func (c *ClientWithResponses) ListUsersWithResponse(ctx context.Context, params *ListUsersParams, reqEditors ...RequestEditorFn) (*ListUsersResponse, error) { - rsp, err := c.ListUsers(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListUsersResponse(rsp) -} - -// CreateUserWithBodyWithResponse request with arbitrary body returning *CreateUserResponse -func (c *ClientWithResponses) CreateUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateUserResponse, error) { - rsp, err := c.CreateUserWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateUserResponse(rsp) -} - -func (c *ClientWithResponses) CreateUserWithResponse(ctx context.Context, body CreateUserJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateUserResponse, error) { - rsp, err := c.CreateUser(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateUserResponse(rsp) -} - -// DeleteUserWithResponse request returning *DeleteUserResponse -func (c *ClientWithResponses) DeleteUserWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteUserResponse, error) { - rsp, err := c.DeleteUser(ctx, id, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteUserResponse(rsp) -} - -// RenameUserWithResponse request returning *RenameUserResponse -func (c *ClientWithResponses) RenameUserWithResponse(ctx context.Context, oldId string, newName string, reqEditors ...RequestEditorFn) (*RenameUserResponse, error) { - rsp, err := c.RenameUser(ctx, oldId, newName, reqEditors...) - if err != nil { - return nil, err - } - return ParseRenameUserResponse(rsp) -} - -// ParseListApiKeysResponse parses an HTTP response from a ListApiKeysWithResponse call -func ParseListApiKeysResponse(rsp *http.Response) (*ListApiKeysResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListApiKeysResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListApiKeysOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseCreateApiKeyResponse parses an HTTP response from a CreateApiKeyWithResponse call -func ParseCreateApiKeyResponse(rsp *http.Response) (*CreateApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreateApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CreateApiKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseExpireApiKeyResponse parses an HTTP response from a ExpireApiKeyWithResponse call -func ParseExpireApiKeyResponse(rsp *http.Response) (*ExpireApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ExpireApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ExpireApiKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseDeleteApiKeyResponse parses an HTTP response from a DeleteApiKeyWithResponse call -func ParseDeleteApiKeyResponse(rsp *http.Response) (*DeleteApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeleteApiKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseAuthApproveResponse parses an HTTP response from a AuthApproveWithResponse call -func ParseAuthApproveResponse(rsp *http.Response) (*AuthApproveResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &AuthApproveResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthApproveOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseAuthRegisterResponse parses an HTTP response from a AuthRegisterWithResponse call -func ParseAuthRegisterResponse(rsp *http.Response) (*AuthRegisterResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &AuthRegisterResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthRegisterOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseAuthRejectResponse parses an HTTP response from a AuthRejectWithResponse call -func ParseAuthRejectResponse(rsp *http.Response) (*AuthRejectResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &AuthRejectResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthRejectOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseDebugCreateNodeResponse parses an HTTP response from a DebugCreateNodeWithResponse call -func ParseDebugCreateNodeResponse(rsp *http.Response) (*DebugCreateNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DebugCreateNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseHealthResponse parses an HTTP response from a HealthWithResponse call -func ParseHealthResponse(rsp *http.Response) (*HealthResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &HealthResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest HealthResponseBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseListNodesResponse parses an HTTP response from a ListNodesWithResponse call -func ParseListNodesResponse(rsp *http.Response) (*ListNodesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListNodesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListNodesOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseBackfillNodeIPsResponse parses an HTTP response from a BackfillNodeIPsWithResponse call -func ParseBackfillNodeIPsResponse(rsp *http.Response) (*BackfillNodeIPsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &BackfillNodeIPsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest BackfillNodeIPsOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseRegisterNodeResponse parses an HTTP response from a RegisterNodeWithResponse call -func ParseRegisterNodeResponse(rsp *http.Response) (*RegisterNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RegisterNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseDeleteNodeResponse parses an HTTP response from a DeleteNodeWithResponse call -func ParseDeleteNodeResponse(rsp *http.Response) (*DeleteNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeleteNodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseGetNodeResponse parses an HTTP response from a GetNodeWithResponse call -func ParseGetNodeResponse(rsp *http.Response) (*GetNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseSetApprovedRoutesResponse parses an HTTP response from a SetApprovedRoutesWithResponse call -func ParseSetApprovedRoutesResponse(rsp *http.Response) (*SetApprovedRoutesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetApprovedRoutesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseExpireNodeResponse parses an HTTP response from a ExpireNodeWithResponse call -func ParseExpireNodeResponse(rsp *http.Response) (*ExpireNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ExpireNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseRenameNodeResponse parses an HTTP response from a RenameNodeWithResponse call -func ParseRenameNodeResponse(rsp *http.Response) (*RenameNodeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RenameNodeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseSetTagsResponse parses an HTTP response from a SetTagsWithResponse call -func ParseSetTagsResponse(rsp *http.Response) (*SetTagsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetTagsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest NodeOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseGetPolicyResponse parses an HTTP response from a GetPolicyWithResponse call -func ParseGetPolicyResponse(rsp *http.Response) (*GetPolicyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetPolicyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PolicyResponseBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseSetPolicyResponse parses an HTTP response from a SetPolicyWithResponse call -func ParseSetPolicyResponse(rsp *http.Response) (*SetPolicyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetPolicyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PolicyResponseBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseCheckPolicyResponse parses an HTTP response from a CheckPolicyWithResponse call -func ParseCheckPolicyResponse(rsp *http.Response) (*CheckPolicyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CheckPolicyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CheckPolicyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseDeletePreAuthKeyResponse parses an HTTP response from a DeletePreAuthKeyWithResponse call -func ParseDeletePreAuthKeyResponse(rsp *http.Response) (*DeletePreAuthKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeletePreAuthKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeletePreAuthKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseListPreAuthKeysResponse parses an HTTP response from a ListPreAuthKeysWithResponse call -func ParseListPreAuthKeysResponse(rsp *http.Response) (*ListPreAuthKeysResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListPreAuthKeysResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListPreAuthKeysOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseCreatePreAuthKeyResponse parses an HTTP response from a CreatePreAuthKeyWithResponse call -func ParseCreatePreAuthKeyResponse(rsp *http.Response) (*CreatePreAuthKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreatePreAuthKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PreAuthKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseExpirePreAuthKeyResponse parses an HTTP response from a ExpirePreAuthKeyWithResponse call -func ParseExpirePreAuthKeyResponse(rsp *http.Response) (*ExpirePreAuthKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ExpirePreAuthKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ExpirePreAuthKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseListUsersResponse parses an HTTP response from a ListUsersWithResponse call -func ParseListUsersResponse(rsp *http.Response) (*ListUsersResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListUsersResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListUsersOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseCreateUserResponse parses an HTTP response from a CreateUserWithResponse call -func ParseCreateUserResponse(rsp *http.Response) (*CreateUserResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreateUserResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseDeleteUserResponse parses an HTTP response from a DeleteUserWithResponse call -func ParseDeleteUserResponse(rsp *http.Response) (*DeleteUserResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteUserResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeleteUserOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} - -// ParseRenameUserResponse parses an HTTP response from a RenameUserWithResponse call -func ParseRenameUserResponse(rsp *http.Response) (*RenameUserResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RenameUserResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UserOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSONDefault = &dest - - } - - return response, nil -} diff --git a/gen/client/v2/client.gen.go b/gen/client/v2/client.gen.go deleted file mode 100644 index c31b47ab1..000000000 --- a/gen/client/v2/client.gen.go +++ /dev/null @@ -1,4013 +0,0 @@ -// Package clientv2 provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.7.1 DO NOT EDIT. -package clientv2 - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" - - "github.com/oapi-codegen/runtime" - openapi_types "github.com/oapi-codegen/runtime/types" -) - -const ( - BasicAuthScopes basicAuthContextKey = "basicAuth.Scopes" - BearerAuthScopes bearerAuthContextKey = "bearerAuth.Scopes" -) - -// CreateKeyRequest defines model for CreateKeyRequest. -type CreateKeyRequest struct { - Capabilities *KeyCapabilities `json:"capabilities,omitempty"` - Description *string `json:"description,omitempty"` - - // ExpirySeconds Lifetime in seconds; defaults to 90 days. Auth keys only. - ExpirySeconds *int64 `json:"expirySeconds,omitempty"` - - // KeyType Key kind: "auth" (default) or "client" (OAuth client). - KeyType *string `json:"keyType,omitempty"` - - // Scopes OAuth scopes granted to the client. keyType=client only. - Scopes *[]string `json:"scopes,omitempty"` - - // Tags Tags the client may assign. keyType=client only. - Tags *[]string `json:"tags,omitempty"` -} - -// DeleteKeyOutputBody defines model for DeleteKeyOutputBody. -type DeleteKeyOutputBody = map[string]interface{} - -// Device defines model for Device. -type Device struct { - Addresses []string `json:"addresses"` - AdvertisedRoutes *[]string `json:"advertisedRoutes,omitempty"` - Authorized bool `json:"authorized"` - ClientVersion string `json:"clientVersion"` - Created time.Time `json:"created"` - EnabledRoutes *[]string `json:"enabledRoutes,omitempty"` - Expires *time.Time `json:"expires,omitempty"` - Hostname string `json:"hostname"` - Id string `json:"id"` - IsEphemeral bool `json:"isEphemeral"` - KeyExpiryDisabled bool `json:"keyExpiryDisabled"` - LastSeen *time.Time `json:"lastSeen,omitempty"` - MachineKey string `json:"machineKey"` - Name string `json:"name"` - NodeId string `json:"nodeId"` - NodeKey string `json:"nodeKey"` - Os string `json:"os"` - Tags []string `json:"tags"` - UpdateAvailable bool `json:"updateAvailable"` - User string `json:"user"` -} - -// DeviceRoutes defines model for DeviceRoutes. -type DeviceRoutes struct { - AdvertisedRoutes []string `json:"advertisedRoutes"` - EnabledRoutes []string `json:"enabledRoutes"` -} - -// EmptyOutputBody defines model for EmptyOutputBody. -type EmptyOutputBody = map[string]interface{} - -// ErrorDetail defines model for ErrorDetail. -type ErrorDetail struct { - // Location Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id' - Location *string `json:"location,omitempty"` - - // Message Error message text - Message *string `json:"message,omitempty"` - - // Value The value at the given location - Value interface{} `json:"value,omitempty"` -} - -// ErrorModel defines model for ErrorModel. -type ErrorModel struct { - // Detail A human-readable explanation specific to this occurrence of the problem. - Detail *string `json:"detail,omitempty"` - - // Errors Optional list of individual error details - Errors *[]ErrorDetail `json:"errors,omitempty"` - - // Instance A URI reference that identifies the specific occurrence of the problem. - Instance *string `json:"instance,omitempty"` - - // Status HTTP status code - Status *int64 `json:"status,omitempty"` - - // Title A short, human-readable summary of the problem type. This value should not change between occurrences of the error. - Title *string `json:"title,omitempty"` - - // Type A URI reference to human-readable documentation for the error. - Type *string `json:"type,omitempty"` -} - -// Key defines model for Key. -type Key struct { - Capabilities KeyCapabilities `json:"capabilities"` - Created time.Time `json:"created"` - Description *string `json:"description,omitempty"` - Expires *time.Time `json:"expires,omitempty"` - ExpirySeconds *int64 `json:"expirySeconds,omitempty"` - Id string `json:"id"` - Invalid bool `json:"invalid"` - Key *string `json:"key,omitempty"` - KeyType string `json:"keyType"` - Revoked *time.Time `json:"revoked,omitempty"` - Scopes *[]string `json:"scopes,omitempty"` - Tags *[]string `json:"tags,omitempty"` - UserId *string `json:"userId,omitempty"` -} - -// KeyCapabilities defines model for KeyCapabilities. -type KeyCapabilities struct { - Devices KeyDeviceCapabilities `json:"devices"` -} - -// KeyDeviceCapabilities defines model for KeyDeviceCapabilities. -type KeyDeviceCapabilities struct { - Create KeyDeviceCreateCapabilities `json:"create"` -} - -// KeyDeviceCreateCapabilities defines model for KeyDeviceCreateCapabilities. -type KeyDeviceCreateCapabilities struct { - Ephemeral bool `json:"ephemeral"` - Preauthorized bool `json:"preauthorized"` - Reusable bool `json:"reusable"` - Tags *[]string `json:"tags"` -} - -// ListDevicesOutputBody defines model for ListDevicesOutputBody. -type ListDevicesOutputBody struct { - Devices []Device `json:"devices"` -} - -// ListKeysOutputBody defines model for ListKeysOutputBody. -type ListKeysOutputBody struct { - Keys []Key `json:"keys"` -} - -// ListUsersOutputBody defines model for ListUsersOutputBody. -type ListUsersOutputBody struct { - Users []User `json:"users"` -} - -// SetAuthorizedRequest defines model for SetAuthorizedRequest. -type SetAuthorizedRequest struct { - Authorized bool `json:"authorized"` -} - -// SetKeyRequest defines model for SetKeyRequest. -type SetKeyRequest struct { - KeyExpiryDisabled bool `json:"keyExpiryDisabled"` -} - -// SetNameRequest defines model for SetNameRequest. -type SetNameRequest struct { - Name string `json:"name"` -} - -// SetSubnetRoutesRequest defines model for SetSubnetRoutesRequest. -type SetSubnetRoutesRequest struct { - Routes []string `json:"routes"` -} - -// SetTagsRequest defines model for SetTagsRequest. -type SetTagsRequest struct { - Tags *[]string `json:"tags"` -} - -// TailnetSettings defines model for TailnetSettings. -type TailnetSettings struct { - AclsExternalLink string `json:"aclsExternalLink"` - AclsExternallyManagedOn bool `json:"aclsExternallyManagedOn"` - DevicesApprovalOn bool `json:"devicesApprovalOn"` - DevicesAutoUpdatesOn bool `json:"devicesAutoUpdatesOn"` - DevicesKeyDurationDays int64 `json:"devicesKeyDurationDays"` - HttpsEnabled bool `json:"httpsEnabled"` - NetworkFlowLoggingOn bool `json:"networkFlowLoggingOn"` - PostureIdentityCollectionOn bool `json:"postureIdentityCollectionOn"` - RegionalRoutingOn bool `json:"regionalRoutingOn"` - UsersApprovalOn bool `json:"usersApprovalOn"` - UsersRoleAllowedToJoinExternalTailnets string `json:"usersRoleAllowedToJoinExternalTailnets"` -} - -// User defines model for User. -type User struct { - Created time.Time `json:"created"` - CurrentlyConnected bool `json:"currentlyConnected"` - DeviceCount int64 `json:"deviceCount"` - DisplayName string `json:"displayName"` - Id string `json:"id"` - LastSeen time.Time `json:"lastSeen"` - LoginName string `json:"loginName"` - ProfilePicUrl string `json:"profilePicUrl"` - Role string `json:"role"` - Status string `json:"status"` - TailnetId string `json:"tailnetId"` - Type string `json:"type"` -} - -// basicAuthContextKey is the context key for basicAuth security scheme -type basicAuthContextKey string - -// bearerAuthContextKey is the context key for bearerAuth security scheme -type bearerAuthContextKey string - -// DeleteDeviceParams defines parameters for DeleteDevice. -type DeleteDeviceParams struct { - // Fields Set to "all" for route fields. - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetDeviceParams defines parameters for GetDevice. -type GetDeviceParams struct { - // Fields Set to "all" for route fields. - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetDeviceRoutesParams defines parameters for GetDeviceRoutes. -type GetDeviceRoutesParams struct { - // Fields Set to "all" for route fields. - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetACLParams defines parameters for GetACL. -type GetACLParams struct { - // Details Accepted for compatibility; ignored. - Details *bool `form:"details,omitempty" json:"details,omitempty"` - Accept *string `json:"Accept,omitempty"` -} - -// SetACLJSONBody defines parameters for SetACL. -type SetACLJSONBody = openapi_types.File - -// SetACLParams defines parameters for SetACL. -type SetACLParams struct { - IfMatch *string `json:"If-Match,omitempty"` - Accept *string `json:"Accept,omitempty"` -} - -// ListDevicesParams defines parameters for ListDevices. -type ListDevicesParams struct { - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// ListKeysParams defines parameters for ListKeys. -type ListKeysParams struct { - // All Accepted for compatibility; Headscale returns all keys. - All *bool `form:"all,omitempty" json:"all,omitempty"` -} - -// UpdateTailnetSettingsJSONBody defines parameters for UpdateTailnetSettings. -type UpdateTailnetSettingsJSONBody = interface{} - -// ListUsersParams defines parameters for ListUsers. -type ListUsersParams struct { - // Type Filter by user type; Headscale users are all "member". - Type *string `form:"type,omitempty" json:"type,omitempty"` - - // Role Filter by user role; Headscale users are all "member". - Role *string `form:"role,omitempty" json:"role,omitempty"` -} - -// AuthorizeDeviceJSONRequestBody defines body for AuthorizeDevice for application/json ContentType. -type AuthorizeDeviceJSONRequestBody = SetAuthorizedRequest - -// SetDeviceKeyJSONRequestBody defines body for SetDeviceKey for application/json ContentType. -type SetDeviceKeyJSONRequestBody = SetKeyRequest - -// SetDeviceNameJSONRequestBody defines body for SetDeviceName for application/json ContentType. -type SetDeviceNameJSONRequestBody = SetNameRequest - -// SetDeviceRoutesJSONRequestBody defines body for SetDeviceRoutes for application/json ContentType. -type SetDeviceRoutesJSONRequestBody = SetSubnetRoutesRequest - -// SetDeviceTagsJSONRequestBody defines body for SetDeviceTags for application/json ContentType. -type SetDeviceTagsJSONRequestBody = SetTagsRequest - -// SetACLJSONRequestBody defines body for SetACL for application/json ContentType. -type SetACLJSONRequestBody = SetACLJSONBody - -// CreateKeyJSONRequestBody defines body for CreateKey for application/json ContentType. -type CreateKeyJSONRequestBody = CreateKeyRequest - -// UpdateTailnetSettingsJSONRequestBody defines body for UpdateTailnetSettings for application/json ContentType. -type UpdateTailnetSettingsJSONRequestBody = UpdateTailnetSettingsJSONBody - -// RequestEditorFn is the function signature for the RequestEditor callback function -type RequestEditorFn func(ctx context.Context, req *http.Request) error - -// Doer performs HTTP requests. -// -// The standard http.Client implements this interface. -type HttpRequestDoer interface { - Do(req *http.Request) (*http.Response, error) -} - -// Client which conforms to the OpenAPI3 specification for this service. -type Client struct { - // The endpoint of the server conforming to this interface, with scheme, - // https://api.deepmap.com for example. This can contain a path relative - // to the server, such as https://api.deepmap.com/dev-test, and all the - // paths in the swagger spec will be appended to the server. - Server string - - // Doer for performing requests, typically a *http.Client with any - // customized settings, such as certificate chains. - Client HttpRequestDoer - - // A list of callbacks for modifying requests which are generated before sending over - // the network. - RequestEditors []RequestEditorFn -} - -// ClientOption allows setting custom parameters during construction -type ClientOption func(*Client) error - -// Creates a new Client, with reasonable defaults -func NewClient(server string, opts ...ClientOption) (*Client, error) { - // create a client with sane default values - client := Client{ - Server: server, - } - // mutate client and add all optional params - for _, o := range opts { - if err := o(&client); err != nil { - return nil, err - } - } - // ensure the server URL always has a trailing slash - if !strings.HasSuffix(client.Server, "/") { - client.Server += "/" - } - // create httpClient, if not already present - if client.Client == nil { - client.Client = &http.Client{} - } - return &client, nil -} - -// WithHTTPClient allows overriding the default Doer, which is -// automatically created using http.Client. This is useful for tests. -func WithHTTPClient(doer HttpRequestDoer) ClientOption { - return func(c *Client) error { - c.Client = doer - return nil - } -} - -// WithRequestEditorFn allows setting up a callback function, which will be -// called right before sending the request. This can be used to mutate the request. -func WithRequestEditorFn(fn RequestEditorFn) ClientOption { - return func(c *Client) error { - c.RequestEditors = append(c.RequestEditors, fn) - return nil - } -} - -// The interface specification for the client above. -type ClientInterface interface { - // DeleteDevice request - DeleteDevice(ctx context.Context, id string, params *DeleteDeviceParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetDevice request - GetDevice(ctx context.Context, id string, params *GetDeviceParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // AuthorizeDeviceWithBody request with any body - AuthorizeDeviceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - AuthorizeDevice(ctx context.Context, id string, body AuthorizeDeviceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetDeviceKeyWithBody request with any body - SetDeviceKeyWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetDeviceKey(ctx context.Context, id string, body SetDeviceKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetDeviceNameWithBody request with any body - SetDeviceNameWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetDeviceName(ctx context.Context, id string, body SetDeviceNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetDeviceRoutes request - GetDeviceRoutes(ctx context.Context, id string, params *GetDeviceRoutesParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetDeviceRoutesWithBody request with any body - SetDeviceRoutesWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetDeviceRoutes(ctx context.Context, id string, body SetDeviceRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetDeviceTagsWithBody request with any body - SetDeviceTagsWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetDeviceTags(ctx context.Context, id string, body SetDeviceTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetACL request - GetACL(ctx context.Context, tailnet string, params *GetACLParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // SetACLWithBody request with any body - SetACLWithBody(ctx context.Context, tailnet string, params *SetACLParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - SetACL(ctx context.Context, tailnet string, params *SetACLParams, body SetACLJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListDevices request - ListDevices(ctx context.Context, tailnet string, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListKeys request - ListKeys(ctx context.Context, tailnet string, params *ListKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateKeyWithBody request with any body - CreateKeyWithBody(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateKey(ctx context.Context, tailnet string, body CreateKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // DeleteKey request - DeleteKey(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetKey request - GetKey(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetTailnetSettings request - GetTailnetSettings(ctx context.Context, tailnet string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // UpdateTailnetSettingsWithBody request with any body - UpdateTailnetSettingsWithBody(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - UpdateTailnetSettings(ctx context.Context, tailnet string, body UpdateTailnetSettingsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListUsers request - ListUsers(ctx context.Context, tailnet string, params *ListUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetUser request - GetUser(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) -} - -func (c *Client) DeleteDevice(ctx context.Context, id string, params *DeleteDeviceParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteDeviceRequest(c.Server, id, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetDevice(ctx context.Context, id string, params *GetDeviceParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetDeviceRequest(c.Server, id, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthorizeDeviceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthorizeDeviceRequestWithBody(c.Server, id, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) AuthorizeDevice(ctx context.Context, id string, body AuthorizeDeviceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewAuthorizeDeviceRequest(c.Server, id, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceKeyWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceKeyRequestWithBody(c.Server, id, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceKey(ctx context.Context, id string, body SetDeviceKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceKeyRequest(c.Server, id, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceNameWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceNameRequestWithBody(c.Server, id, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceName(ctx context.Context, id string, body SetDeviceNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceNameRequest(c.Server, id, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetDeviceRoutes(ctx context.Context, id string, params *GetDeviceRoutesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetDeviceRoutesRequest(c.Server, id, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceRoutesWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceRoutesRequestWithBody(c.Server, id, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceRoutes(ctx context.Context, id string, body SetDeviceRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceRoutesRequest(c.Server, id, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceTagsWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceTagsRequestWithBody(c.Server, id, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetDeviceTags(ctx context.Context, id string, body SetDeviceTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetDeviceTagsRequest(c.Server, id, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetACL(ctx context.Context, tailnet string, params *GetACLParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetACLRequest(c.Server, tailnet, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetACLWithBody(ctx context.Context, tailnet string, params *SetACLParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetACLRequestWithBody(c.Server, tailnet, params, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) SetACL(ctx context.Context, tailnet string, params *SetACLParams, body SetACLJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetACLRequest(c.Server, tailnet, params, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListDevices(ctx context.Context, tailnet string, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListDevicesRequest(c.Server, tailnet, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListKeys(ctx context.Context, tailnet string, params *ListKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListKeysRequest(c.Server, tailnet, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateKeyWithBody(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateKeyRequestWithBody(c.Server, tailnet, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateKey(ctx context.Context, tailnet string, body CreateKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateKeyRequest(c.Server, tailnet, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) DeleteKey(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteKeyRequest(c.Server, tailnet, keyId) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetKey(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetKeyRequest(c.Server, tailnet, keyId) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetTailnetSettings(ctx context.Context, tailnet string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetTailnetSettingsRequest(c.Server, tailnet) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) UpdateTailnetSettingsWithBody(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateTailnetSettingsRequestWithBody(c.Server, tailnet, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) UpdateTailnetSettings(ctx context.Context, tailnet string, body UpdateTailnetSettingsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateTailnetSettingsRequest(c.Server, tailnet, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListUsers(ctx context.Context, tailnet string, params *ListUsersParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListUsersRequest(c.Server, tailnet, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetUser(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetUserRequest(c.Server, id) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -// NewDeleteDeviceRequest generates requests for DeleteDevice -func NewDeleteDeviceRequest(server string, id string, params *DeleteDeviceParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Fields != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "fields", *params.Fields, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetDeviceRequest generates requests for GetDevice -func NewGetDeviceRequest(server string, id string, params *GetDeviceParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Fields != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "fields", *params.Fields, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewAuthorizeDeviceRequest calls the generic AuthorizeDevice builder with application/json body -func NewAuthorizeDeviceRequest(server string, id string, body AuthorizeDeviceJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewAuthorizeDeviceRequestWithBody(server, id, "application/json", bodyReader) -} - -// NewAuthorizeDeviceRequestWithBody generates requests for AuthorizeDevice with any type of body -func NewAuthorizeDeviceRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/authorized", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewSetDeviceKeyRequest calls the generic SetDeviceKey builder with application/json body -func NewSetDeviceKeyRequest(server string, id string, body SetDeviceKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetDeviceKeyRequestWithBody(server, id, "application/json", bodyReader) -} - -// NewSetDeviceKeyRequestWithBody generates requests for SetDeviceKey with any type of body -func NewSetDeviceKeyRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/key", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewSetDeviceNameRequest calls the generic SetDeviceName builder with application/json body -func NewSetDeviceNameRequest(server string, id string, body SetDeviceNameJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetDeviceNameRequestWithBody(server, id, "application/json", bodyReader) -} - -// NewSetDeviceNameRequestWithBody generates requests for SetDeviceName with any type of body -func NewSetDeviceNameRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/name", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewGetDeviceRoutesRequest generates requests for GetDeviceRoutes -func NewGetDeviceRoutesRequest(server string, id string, params *GetDeviceRoutesParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/routes", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Fields != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "fields", *params.Fields, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewSetDeviceRoutesRequest calls the generic SetDeviceRoutes builder with application/json body -func NewSetDeviceRoutesRequest(server string, id string, body SetDeviceRoutesJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetDeviceRoutesRequestWithBody(server, id, "application/json", bodyReader) -} - -// NewSetDeviceRoutesRequestWithBody generates requests for SetDeviceRoutes with any type of body -func NewSetDeviceRoutesRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/routes", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewSetDeviceTagsRequest calls the generic SetDeviceTags builder with application/json body -func NewSetDeviceTagsRequest(server string, id string, body SetDeviceTagsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetDeviceTagsRequestWithBody(server, id, "application/json", bodyReader) -} - -// NewSetDeviceTagsRequestWithBody generates requests for SetDeviceTags with any type of body -func NewSetDeviceTagsRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/device/%s/tags", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewGetACLRequest generates requests for GetACL -func NewGetACLRequest(server string, tailnet string, params *GetACLParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/acl", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Details != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "details", *params.Details, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "boolean", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - if params != nil { - - if params.Accept != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Accept", *params.Accept, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", headerParam0) - } - - } - - return req, nil -} - -// NewSetACLRequest calls the generic SetACL builder with application/json body -func NewSetACLRequest(server string, tailnet string, params *SetACLParams, body SetACLJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetACLRequestWithBody(server, tailnet, params, "application/json", bodyReader) -} - -// NewSetACLRequestWithBody generates requests for SetACL with any type of body -func NewSetACLRequestWithBody(server string, tailnet string, params *SetACLParams, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/acl", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - if params != nil { - - if params.IfMatch != nil { - var headerParam0 string - - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "If-Match", *params.IfMatch, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - req.Header.Set("If-Match", headerParam0) - } - - if params.Accept != nil { - var headerParam1 string - - headerParam1, err = runtime.StyleParamWithOptions("simple", false, "Accept", *params.Accept, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", headerParam1) - } - - } - - return req, nil -} - -// NewListDevicesRequest generates requests for ListDevices -func NewListDevicesRequest(server string, tailnet string, params *ListDevicesParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/devices", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Fields != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "fields", *params.Fields, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewListKeysRequest generates requests for ListKeys -func NewListKeysRequest(server string, tailnet string, params *ListKeysParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/keys", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.All != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "all", *params.All, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "boolean", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreateKeyRequest calls the generic CreateKey builder with application/json body -func NewCreateKeyRequest(server string, tailnet string, body CreateKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateKeyRequestWithBody(server, tailnet, "application/json", bodyReader) -} - -// NewCreateKeyRequestWithBody generates requests for CreateKey with any type of body -func NewCreateKeyRequestWithBody(server string, tailnet string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/keys", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteKeyRequest generates requests for DeleteKey -func NewDeleteKeyRequest(server string, tailnet string, keyId string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "keyId", keyId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/keys/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetKeyRequest generates requests for GetKey -func NewGetKeyRequest(server string, tailnet string, keyId string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "keyId", keyId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/keys/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetTailnetSettingsRequest generates requests for GetTailnetSettings -func NewGetTailnetSettingsRequest(server string, tailnet string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/settings", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewUpdateTailnetSettingsRequest calls the generic UpdateTailnetSettings builder with application/json body -func NewUpdateTailnetSettingsRequest(server string, tailnet string, body UpdateTailnetSettingsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdateTailnetSettingsRequestWithBody(server, tailnet, "application/json", bodyReader) -} - -// NewUpdateTailnetSettingsRequestWithBody generates requests for UpdateTailnetSettings with any type of body -func NewUpdateTailnetSettingsRequestWithBody(server string, tailnet string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/settings", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPatch, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewListUsersRequest generates requests for ListUsers -func NewListUsersRequest(server string, tailnet string, params *ListUsersParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "tailnet", tailnet, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/tailnet/%s/users", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Type != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "type", *params.Type, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Role != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", false, "role", *params.Role, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetUserRequest generates requests for GetUser -func NewGetUserRequest(server string, id string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v2/users/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} - -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} - -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) - if err != nil { - return nil, err - } - return &ClientWithResponses{client}, nil -} - -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil - } -} - -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // DeleteDeviceWithResponse request - DeleteDeviceWithResponse(ctx context.Context, id string, params *DeleteDeviceParams, reqEditors ...RequestEditorFn) (*DeleteDeviceResponse, error) - - // GetDeviceWithResponse request - GetDeviceWithResponse(ctx context.Context, id string, params *GetDeviceParams, reqEditors ...RequestEditorFn) (*GetDeviceResponse, error) - - // AuthorizeDeviceWithBodyWithResponse request with any body - AuthorizeDeviceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthorizeDeviceResponse, error) - - AuthorizeDeviceWithResponse(ctx context.Context, id string, body AuthorizeDeviceJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthorizeDeviceResponse, error) - - // SetDeviceKeyWithBodyWithResponse request with any body - SetDeviceKeyWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceKeyResponse, error) - - SetDeviceKeyWithResponse(ctx context.Context, id string, body SetDeviceKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceKeyResponse, error) - - // SetDeviceNameWithBodyWithResponse request with any body - SetDeviceNameWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceNameResponse, error) - - SetDeviceNameWithResponse(ctx context.Context, id string, body SetDeviceNameJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceNameResponse, error) - - // GetDeviceRoutesWithResponse request - GetDeviceRoutesWithResponse(ctx context.Context, id string, params *GetDeviceRoutesParams, reqEditors ...RequestEditorFn) (*GetDeviceRoutesResponse, error) - - // SetDeviceRoutesWithBodyWithResponse request with any body - SetDeviceRoutesWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceRoutesResponse, error) - - SetDeviceRoutesWithResponse(ctx context.Context, id string, body SetDeviceRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceRoutesResponse, error) - - // SetDeviceTagsWithBodyWithResponse request with any body - SetDeviceTagsWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceTagsResponse, error) - - SetDeviceTagsWithResponse(ctx context.Context, id string, body SetDeviceTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceTagsResponse, error) - - // GetACLWithResponse request - GetACLWithResponse(ctx context.Context, tailnet string, params *GetACLParams, reqEditors ...RequestEditorFn) (*GetACLResponse, error) - - // SetACLWithBodyWithResponse request with any body - SetACLWithBodyWithResponse(ctx context.Context, tailnet string, params *SetACLParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetACLResponse, error) - - SetACLWithResponse(ctx context.Context, tailnet string, params *SetACLParams, body SetACLJSONRequestBody, reqEditors ...RequestEditorFn) (*SetACLResponse, error) - - // ListDevicesWithResponse request - ListDevicesWithResponse(ctx context.Context, tailnet string, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*ListDevicesResponse, error) - - // ListKeysWithResponse request - ListKeysWithResponse(ctx context.Context, tailnet string, params *ListKeysParams, reqEditors ...RequestEditorFn) (*ListKeysResponse, error) - - // CreateKeyWithBodyWithResponse request with any body - CreateKeyWithBodyWithResponse(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateKeyResponse, error) - - CreateKeyWithResponse(ctx context.Context, tailnet string, body CreateKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateKeyResponse, error) - - // DeleteKeyWithResponse request - DeleteKeyWithResponse(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*DeleteKeyResponse, error) - - // GetKeyWithResponse request - GetKeyWithResponse(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*GetKeyResponse, error) - - // GetTailnetSettingsWithResponse request - GetTailnetSettingsWithResponse(ctx context.Context, tailnet string, reqEditors ...RequestEditorFn) (*GetTailnetSettingsResponse, error) - - // UpdateTailnetSettingsWithBodyWithResponse request with any body - UpdateTailnetSettingsWithBodyWithResponse(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateTailnetSettingsResponse, error) - - UpdateTailnetSettingsWithResponse(ctx context.Context, tailnet string, body UpdateTailnetSettingsJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateTailnetSettingsResponse, error) - - // ListUsersWithResponse request - ListUsersWithResponse(ctx context.Context, tailnet string, params *ListUsersParams, reqEditors ...RequestEditorFn) (*ListUsersResponse, error) - - // GetUserWithResponse request - GetUserWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetUserResponse, error) -} - -type DeleteDeviceResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EmptyOutputBody - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeleteDeviceResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteDeviceResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteDeviceResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetDeviceResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Device - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetDeviceResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetDeviceResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetDeviceResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type AuthorizeDeviceResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EmptyOutputBody - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r AuthorizeDeviceResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r AuthorizeDeviceResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AuthorizeDeviceResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetDeviceKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EmptyOutputBody - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetDeviceKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetDeviceKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetDeviceKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetDeviceNameResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EmptyOutputBody - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetDeviceNameResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetDeviceNameResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetDeviceNameResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetDeviceRoutesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeviceRoutes - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetDeviceRoutesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetDeviceRoutesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetDeviceRoutesResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetDeviceRoutesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeviceRoutes - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetDeviceRoutesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetDeviceRoutesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetDeviceRoutesResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetDeviceTagsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *EmptyOutputBody - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetDeviceTagsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetDeviceTagsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetDeviceTagsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetACLResponse struct { - Body []byte - HTTPResponse *http.Response - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetACLResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetACLResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetACLResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type SetACLResponse struct { - Body []byte - HTTPResponse *http.Response - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON412 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r SetACLResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SetACLResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetACLResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListDevicesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListDevicesOutputBody - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListDevicesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListDevicesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListDevicesResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListKeysResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListKeysOutputBody - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListKeysResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListKeysResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListKeysResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CreateKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Key - ApplicationproblemJSON400 *ErrorModel - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r CreateKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CreateKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type DeleteKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *DeleteKeyOutputBody - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r DeleteKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Key - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetTailnetSettingsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *TailnetSettings - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetTailnetSettingsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetTailnetSettingsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetTailnetSettingsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type UpdateTailnetSettingsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *TailnetSettings - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel - ApplicationproblemJSON501 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r UpdateTailnetSettingsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r UpdateTailnetSettingsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r UpdateTailnetSettingsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type ListUsersResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListUsersOutputBody - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r ListUsersResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListUsersResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListUsersResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type GetUserResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *User - ApplicationproblemJSON401 *ErrorModel - ApplicationproblemJSON403 *ErrorModel - ApplicationproblemJSON404 *ErrorModel - ApplicationproblemJSON422 *ErrorModel - ApplicationproblemJSON500 *ErrorModel -} - -// Status returns HTTPResponse.Status -func (r GetUserResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetUserResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetUserResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -// DeleteDeviceWithResponse request returning *DeleteDeviceResponse -func (c *ClientWithResponses) DeleteDeviceWithResponse(ctx context.Context, id string, params *DeleteDeviceParams, reqEditors ...RequestEditorFn) (*DeleteDeviceResponse, error) { - rsp, err := c.DeleteDevice(ctx, id, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteDeviceResponse(rsp) -} - -// GetDeviceWithResponse request returning *GetDeviceResponse -func (c *ClientWithResponses) GetDeviceWithResponse(ctx context.Context, id string, params *GetDeviceParams, reqEditors ...RequestEditorFn) (*GetDeviceResponse, error) { - rsp, err := c.GetDevice(ctx, id, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetDeviceResponse(rsp) -} - -// AuthorizeDeviceWithBodyWithResponse request with arbitrary body returning *AuthorizeDeviceResponse -func (c *ClientWithResponses) AuthorizeDeviceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AuthorizeDeviceResponse, error) { - rsp, err := c.AuthorizeDeviceWithBody(ctx, id, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthorizeDeviceResponse(rsp) -} - -func (c *ClientWithResponses) AuthorizeDeviceWithResponse(ctx context.Context, id string, body AuthorizeDeviceJSONRequestBody, reqEditors ...RequestEditorFn) (*AuthorizeDeviceResponse, error) { - rsp, err := c.AuthorizeDevice(ctx, id, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseAuthorizeDeviceResponse(rsp) -} - -// SetDeviceKeyWithBodyWithResponse request with arbitrary body returning *SetDeviceKeyResponse -func (c *ClientWithResponses) SetDeviceKeyWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceKeyResponse, error) { - rsp, err := c.SetDeviceKeyWithBody(ctx, id, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceKeyResponse(rsp) -} - -func (c *ClientWithResponses) SetDeviceKeyWithResponse(ctx context.Context, id string, body SetDeviceKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceKeyResponse, error) { - rsp, err := c.SetDeviceKey(ctx, id, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceKeyResponse(rsp) -} - -// SetDeviceNameWithBodyWithResponse request with arbitrary body returning *SetDeviceNameResponse -func (c *ClientWithResponses) SetDeviceNameWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceNameResponse, error) { - rsp, err := c.SetDeviceNameWithBody(ctx, id, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceNameResponse(rsp) -} - -func (c *ClientWithResponses) SetDeviceNameWithResponse(ctx context.Context, id string, body SetDeviceNameJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceNameResponse, error) { - rsp, err := c.SetDeviceName(ctx, id, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceNameResponse(rsp) -} - -// GetDeviceRoutesWithResponse request returning *GetDeviceRoutesResponse -func (c *ClientWithResponses) GetDeviceRoutesWithResponse(ctx context.Context, id string, params *GetDeviceRoutesParams, reqEditors ...RequestEditorFn) (*GetDeviceRoutesResponse, error) { - rsp, err := c.GetDeviceRoutes(ctx, id, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetDeviceRoutesResponse(rsp) -} - -// SetDeviceRoutesWithBodyWithResponse request with arbitrary body returning *SetDeviceRoutesResponse -func (c *ClientWithResponses) SetDeviceRoutesWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceRoutesResponse, error) { - rsp, err := c.SetDeviceRoutesWithBody(ctx, id, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceRoutesResponse(rsp) -} - -func (c *ClientWithResponses) SetDeviceRoutesWithResponse(ctx context.Context, id string, body SetDeviceRoutesJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceRoutesResponse, error) { - rsp, err := c.SetDeviceRoutes(ctx, id, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceRoutesResponse(rsp) -} - -// SetDeviceTagsWithBodyWithResponse request with arbitrary body returning *SetDeviceTagsResponse -func (c *ClientWithResponses) SetDeviceTagsWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetDeviceTagsResponse, error) { - rsp, err := c.SetDeviceTagsWithBody(ctx, id, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceTagsResponse(rsp) -} - -func (c *ClientWithResponses) SetDeviceTagsWithResponse(ctx context.Context, id string, body SetDeviceTagsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetDeviceTagsResponse, error) { - rsp, err := c.SetDeviceTags(ctx, id, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetDeviceTagsResponse(rsp) -} - -// GetACLWithResponse request returning *GetACLResponse -func (c *ClientWithResponses) GetACLWithResponse(ctx context.Context, tailnet string, params *GetACLParams, reqEditors ...RequestEditorFn) (*GetACLResponse, error) { - rsp, err := c.GetACL(ctx, tailnet, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetACLResponse(rsp) -} - -// SetACLWithBodyWithResponse request with arbitrary body returning *SetACLResponse -func (c *ClientWithResponses) SetACLWithBodyWithResponse(ctx context.Context, tailnet string, params *SetACLParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetACLResponse, error) { - rsp, err := c.SetACLWithBody(ctx, tailnet, params, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetACLResponse(rsp) -} - -func (c *ClientWithResponses) SetACLWithResponse(ctx context.Context, tailnet string, params *SetACLParams, body SetACLJSONRequestBody, reqEditors ...RequestEditorFn) (*SetACLResponse, error) { - rsp, err := c.SetACL(ctx, tailnet, params, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseSetACLResponse(rsp) -} - -// ListDevicesWithResponse request returning *ListDevicesResponse -func (c *ClientWithResponses) ListDevicesWithResponse(ctx context.Context, tailnet string, params *ListDevicesParams, reqEditors ...RequestEditorFn) (*ListDevicesResponse, error) { - rsp, err := c.ListDevices(ctx, tailnet, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListDevicesResponse(rsp) -} - -// ListKeysWithResponse request returning *ListKeysResponse -func (c *ClientWithResponses) ListKeysWithResponse(ctx context.Context, tailnet string, params *ListKeysParams, reqEditors ...RequestEditorFn) (*ListKeysResponse, error) { - rsp, err := c.ListKeys(ctx, tailnet, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListKeysResponse(rsp) -} - -// CreateKeyWithBodyWithResponse request with arbitrary body returning *CreateKeyResponse -func (c *ClientWithResponses) CreateKeyWithBodyWithResponse(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateKeyResponse, error) { - rsp, err := c.CreateKeyWithBody(ctx, tailnet, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateKeyResponse(rsp) -} - -func (c *ClientWithResponses) CreateKeyWithResponse(ctx context.Context, tailnet string, body CreateKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateKeyResponse, error) { - rsp, err := c.CreateKey(ctx, tailnet, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateKeyResponse(rsp) -} - -// DeleteKeyWithResponse request returning *DeleteKeyResponse -func (c *ClientWithResponses) DeleteKeyWithResponse(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*DeleteKeyResponse, error) { - rsp, err := c.DeleteKey(ctx, tailnet, keyId, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteKeyResponse(rsp) -} - -// GetKeyWithResponse request returning *GetKeyResponse -func (c *ClientWithResponses) GetKeyWithResponse(ctx context.Context, tailnet string, keyId string, reqEditors ...RequestEditorFn) (*GetKeyResponse, error) { - rsp, err := c.GetKey(ctx, tailnet, keyId, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetKeyResponse(rsp) -} - -// GetTailnetSettingsWithResponse request returning *GetTailnetSettingsResponse -func (c *ClientWithResponses) GetTailnetSettingsWithResponse(ctx context.Context, tailnet string, reqEditors ...RequestEditorFn) (*GetTailnetSettingsResponse, error) { - rsp, err := c.GetTailnetSettings(ctx, tailnet, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetTailnetSettingsResponse(rsp) -} - -// UpdateTailnetSettingsWithBodyWithResponse request with arbitrary body returning *UpdateTailnetSettingsResponse -func (c *ClientWithResponses) UpdateTailnetSettingsWithBodyWithResponse(ctx context.Context, tailnet string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateTailnetSettingsResponse, error) { - rsp, err := c.UpdateTailnetSettingsWithBody(ctx, tailnet, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateTailnetSettingsResponse(rsp) -} - -func (c *ClientWithResponses) UpdateTailnetSettingsWithResponse(ctx context.Context, tailnet string, body UpdateTailnetSettingsJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateTailnetSettingsResponse, error) { - rsp, err := c.UpdateTailnetSettings(ctx, tailnet, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateTailnetSettingsResponse(rsp) -} - -// ListUsersWithResponse request returning *ListUsersResponse -func (c *ClientWithResponses) ListUsersWithResponse(ctx context.Context, tailnet string, params *ListUsersParams, reqEditors ...RequestEditorFn) (*ListUsersResponse, error) { - rsp, err := c.ListUsers(ctx, tailnet, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListUsersResponse(rsp) -} - -// GetUserWithResponse request returning *GetUserResponse -func (c *ClientWithResponses) GetUserWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetUserResponse, error) { - rsp, err := c.GetUser(ctx, id, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetUserResponse(rsp) -} - -// ParseDeleteDeviceResponse parses an HTTP response from a DeleteDeviceWithResponse call -func ParseDeleteDeviceResponse(rsp *http.Response) (*DeleteDeviceResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteDeviceResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EmptyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetDeviceResponse parses an HTTP response from a GetDeviceWithResponse call -func ParseGetDeviceResponse(rsp *http.Response) (*GetDeviceResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetDeviceResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Device - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseAuthorizeDeviceResponse parses an HTTP response from a AuthorizeDeviceWithResponse call -func ParseAuthorizeDeviceResponse(rsp *http.Response) (*AuthorizeDeviceResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &AuthorizeDeviceResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EmptyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseSetDeviceKeyResponse parses an HTTP response from a SetDeviceKeyWithResponse call -func ParseSetDeviceKeyResponse(rsp *http.Response) (*SetDeviceKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetDeviceKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EmptyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseSetDeviceNameResponse parses an HTTP response from a SetDeviceNameWithResponse call -func ParseSetDeviceNameResponse(rsp *http.Response) (*SetDeviceNameResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetDeviceNameResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EmptyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetDeviceRoutesResponse parses an HTTP response from a GetDeviceRoutesWithResponse call -func ParseGetDeviceRoutesResponse(rsp *http.Response) (*GetDeviceRoutesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetDeviceRoutesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeviceRoutes - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseSetDeviceRoutesResponse parses an HTTP response from a SetDeviceRoutesWithResponse call -func ParseSetDeviceRoutesResponse(rsp *http.Response) (*SetDeviceRoutesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetDeviceRoutesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeviceRoutes - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseSetDeviceTagsResponse parses an HTTP response from a SetDeviceTagsWithResponse call -func ParseSetDeviceTagsResponse(rsp *http.Response) (*SetDeviceTagsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetDeviceTagsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EmptyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetACLResponse parses an HTTP response from a GetACLWithResponse call -func ParseGetACLResponse(rsp *http.Response) (*GetACLResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetACLResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseSetACLResponse parses an HTTP response from a SetACLWithResponse call -func ParseSetACLResponse(rsp *http.Response) (*SetACLResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &SetACLResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 412: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON412 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseListDevicesResponse parses an HTTP response from a ListDevicesWithResponse call -func ParseListDevicesResponse(rsp *http.Response) (*ListDevicesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListDevicesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListDevicesOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseListKeysResponse parses an HTTP response from a ListKeysWithResponse call -func ParseListKeysResponse(rsp *http.Response) (*ListKeysResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListKeysResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListKeysOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseCreateKeyResponse parses an HTTP response from a CreateKeyWithResponse call -func ParseCreateKeyResponse(rsp *http.Response) (*CreateKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreateKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Key - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseDeleteKeyResponse parses an HTTP response from a DeleteKeyWithResponse call -func ParseDeleteKeyResponse(rsp *http.Response) (*DeleteKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DeleteKeyOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetKeyResponse parses an HTTP response from a GetKeyWithResponse call -func ParseGetKeyResponse(rsp *http.Response) (*GetKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Key - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetTailnetSettingsResponse parses an HTTP response from a GetTailnetSettingsWithResponse call -func ParseGetTailnetSettingsResponse(rsp *http.Response) (*GetTailnetSettingsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetTailnetSettingsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest TailnetSettings - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseUpdateTailnetSettingsResponse parses an HTTP response from a UpdateTailnetSettingsWithResponse call -func ParseUpdateTailnetSettingsResponse(rsp *http.Response) (*UpdateTailnetSettingsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &UpdateTailnetSettingsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest TailnetSettings - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 501: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON501 = &dest - - } - - return response, nil -} - -// ParseListUsersResponse parses an HTTP response from a ListUsersWithResponse call -func ParseListUsersResponse(rsp *http.Response) (*ListUsersResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListUsersResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListUsersOutputBody - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} - -// ParseGetUserResponse parses an HTTP response from a GetUserWithResponse call -func ParseGetUserResponse(rsp *http.Response) (*GetUserResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetUserResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest User - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON422 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest ErrorModel - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.ApplicationproblemJSON500 = &dest - - } - - return response, nil -} diff --git a/gen/go/headscale/v1/apikey.pb.go b/gen/go/headscale/v1/apikey.pb.go new file mode 100644 index 000000000..0c8557384 --- /dev/null +++ b/gen/go/headscale/v1/apikey.pb.go @@ -0,0 +1,537 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/apikey.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ApiKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + LastSeen *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApiKey) Reset() { + *x = ApiKey{} + mi := &file_headscale_v1_apikey_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApiKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApiKey) ProtoMessage() {} + +func (x *ApiKey) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApiKey.ProtoReflect.Descriptor instead. +func (*ApiKey) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{0} +} + +func (x *ApiKey) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ApiKey) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +func (x *ApiKey) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +func (x *ApiKey) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *ApiKey) GetLastSeen() *timestamppb.Timestamp { + if x != nil { + return x.LastSeen + } + return nil +} + +type CreateApiKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expiration,proto3" json:"expiration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateApiKeyRequest) Reset() { + *x = CreateApiKeyRequest{} + mi := &file_headscale_v1_apikey_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateApiKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateApiKeyRequest) ProtoMessage() {} + +func (x *CreateApiKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateApiKeyRequest.ProtoReflect.Descriptor instead. +func (*CreateApiKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateApiKeyRequest) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +type CreateApiKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ApiKey string `protobuf:"bytes,1,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateApiKeyResponse) Reset() { + *x = CreateApiKeyResponse{} + mi := &file_headscale_v1_apikey_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateApiKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateApiKeyResponse) ProtoMessage() {} + +func (x *CreateApiKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateApiKeyResponse.ProtoReflect.Descriptor instead. +func (*CreateApiKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateApiKeyResponse) GetApiKey() string { + if x != nil { + return x.ApiKey + } + return "" +} + +type ExpireApiKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` + Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpireApiKeyRequest) Reset() { + *x = ExpireApiKeyRequest{} + mi := &file_headscale_v1_apikey_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpireApiKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireApiKeyRequest) ProtoMessage() {} + +func (x *ExpireApiKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpireApiKeyRequest.ProtoReflect.Descriptor instead. +func (*ExpireApiKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{3} +} + +func (x *ExpireApiKeyRequest) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +func (x *ExpireApiKeyRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +type ExpireApiKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpireApiKeyResponse) Reset() { + *x = ExpireApiKeyResponse{} + mi := &file_headscale_v1_apikey_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpireApiKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireApiKeyResponse) ProtoMessage() {} + +func (x *ExpireApiKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpireApiKeyResponse.ProtoReflect.Descriptor instead. +func (*ExpireApiKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{4} +} + +type ListApiKeysRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListApiKeysRequest) Reset() { + *x = ListApiKeysRequest{} + mi := &file_headscale_v1_apikey_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListApiKeysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListApiKeysRequest) ProtoMessage() {} + +func (x *ListApiKeysRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListApiKeysRequest.ProtoReflect.Descriptor instead. +func (*ListApiKeysRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{5} +} + +type ListApiKeysResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ApiKeys []*ApiKey `protobuf:"bytes,1,rep,name=api_keys,json=apiKeys,proto3" json:"api_keys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListApiKeysResponse) Reset() { + *x = ListApiKeysResponse{} + mi := &file_headscale_v1_apikey_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListApiKeysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListApiKeysResponse) ProtoMessage() {} + +func (x *ListApiKeysResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListApiKeysResponse.ProtoReflect.Descriptor instead. +func (*ListApiKeysResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{6} +} + +func (x *ListApiKeysResponse) GetApiKeys() []*ApiKey { + if x != nil { + return x.ApiKeys + } + return nil +} + +type DeleteApiKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` + Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteApiKeyRequest) Reset() { + *x = DeleteApiKeyRequest{} + mi := &file_headscale_v1_apikey_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteApiKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteApiKeyRequest) ProtoMessage() {} + +func (x *DeleteApiKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteApiKeyRequest.ProtoReflect.Descriptor instead. +func (*DeleteApiKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteApiKeyRequest) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +func (x *DeleteApiKeyRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +type DeleteApiKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteApiKeyResponse) Reset() { + *x = DeleteApiKeyResponse{} + mi := &file_headscale_v1_apikey_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteApiKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteApiKeyResponse) ProtoMessage() {} + +func (x *DeleteApiKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_apikey_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteApiKeyResponse.ProtoReflect.Descriptor instead. +func (*DeleteApiKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{8} +} + +var File_headscale_v1_apikey_proto protoreflect.FileDescriptor + +const file_headscale_v1_apikey_proto_rawDesc = "" + + "\n" + + "\x19headscale/v1/apikey.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe0\x01\n" + + "\x06ApiKey\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x16\n" + + "\x06prefix\x18\x02 \x01(\tR\x06prefix\x12:\n" + + "\n" + + "expiration\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "expiration\x129\n" + + "\n" + + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x127\n" + + "\tlast_seen\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\blastSeen\"Q\n" + + "\x13CreateApiKeyRequest\x12:\n" + + "\n" + + "expiration\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "expiration\"/\n" + + "\x14CreateApiKeyResponse\x12\x17\n" + + "\aapi_key\x18\x01 \x01(\tR\x06apiKey\"=\n" + + "\x13ExpireApiKeyRequest\x12\x16\n" + + "\x06prefix\x18\x01 \x01(\tR\x06prefix\x12\x0e\n" + + "\x02id\x18\x02 \x01(\x04R\x02id\"\x16\n" + + "\x14ExpireApiKeyResponse\"\x14\n" + + "\x12ListApiKeysRequest\"F\n" + + "\x13ListApiKeysResponse\x12/\n" + + "\bapi_keys\x18\x01 \x03(\v2\x14.headscale.v1.ApiKeyR\aapiKeys\"=\n" + + "\x13DeleteApiKeyRequest\x12\x16\n" + + "\x06prefix\x18\x01 \x01(\tR\x06prefix\x12\x0e\n" + + "\x02id\x18\x02 \x01(\x04R\x02id\"\x16\n" + + "\x14DeleteApiKeyResponseB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_apikey_proto_rawDescOnce sync.Once + file_headscale_v1_apikey_proto_rawDescData []byte +) + +func file_headscale_v1_apikey_proto_rawDescGZIP() []byte { + file_headscale_v1_apikey_proto_rawDescOnce.Do(func() { + file_headscale_v1_apikey_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_apikey_proto_rawDesc), len(file_headscale_v1_apikey_proto_rawDesc))) + }) + return file_headscale_v1_apikey_proto_rawDescData +} + +var file_headscale_v1_apikey_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_headscale_v1_apikey_proto_goTypes = []any{ + (*ApiKey)(nil), // 0: headscale.v1.ApiKey + (*CreateApiKeyRequest)(nil), // 1: headscale.v1.CreateApiKeyRequest + (*CreateApiKeyResponse)(nil), // 2: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyRequest)(nil), // 3: headscale.v1.ExpireApiKeyRequest + (*ExpireApiKeyResponse)(nil), // 4: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysRequest)(nil), // 5: headscale.v1.ListApiKeysRequest + (*ListApiKeysResponse)(nil), // 6: headscale.v1.ListApiKeysResponse + (*DeleteApiKeyRequest)(nil), // 7: headscale.v1.DeleteApiKeyRequest + (*DeleteApiKeyResponse)(nil), // 8: headscale.v1.DeleteApiKeyResponse + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp +} +var file_headscale_v1_apikey_proto_depIdxs = []int32{ + 9, // 0: headscale.v1.ApiKey.expiration:type_name -> google.protobuf.Timestamp + 9, // 1: headscale.v1.ApiKey.created_at:type_name -> google.protobuf.Timestamp + 9, // 2: headscale.v1.ApiKey.last_seen:type_name -> google.protobuf.Timestamp + 9, // 3: headscale.v1.CreateApiKeyRequest.expiration:type_name -> google.protobuf.Timestamp + 0, // 4: headscale.v1.ListApiKeysResponse.api_keys:type_name -> headscale.v1.ApiKey + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_headscale_v1_apikey_proto_init() } +func file_headscale_v1_apikey_proto_init() { + if File_headscale_v1_apikey_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_apikey_proto_rawDesc), len(file_headscale_v1_apikey_proto_rawDesc)), + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_apikey_proto_goTypes, + DependencyIndexes: file_headscale_v1_apikey_proto_depIdxs, + MessageInfos: file_headscale_v1_apikey_proto_msgTypes, + }.Build() + File_headscale_v1_apikey_proto = out.File + file_headscale_v1_apikey_proto_goTypes = nil + file_headscale_v1_apikey_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/auth.pb.go b/gen/go/headscale/v1/auth.pb.go new file mode 100644 index 000000000..2d6bf779e --- /dev/null +++ b/gen/go/headscale/v1/auth.pb.go @@ -0,0 +1,351 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/auth.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AuthRegisterRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + AuthId string `protobuf:"bytes,2,opt,name=auth_id,json=authId,proto3" json:"auth_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthRegisterRequest) Reset() { + *x = AuthRegisterRequest{} + mi := &file_headscale_v1_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthRegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthRegisterRequest) ProtoMessage() {} + +func (x *AuthRegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthRegisterRequest.ProtoReflect.Descriptor instead. +func (*AuthRegisterRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthRegisterRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *AuthRegisterRequest) GetAuthId() string { + if x != nil { + return x.AuthId + } + return "" +} + +type AuthRegisterResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthRegisterResponse) Reset() { + *x = AuthRegisterResponse{} + mi := &file_headscale_v1_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthRegisterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthRegisterResponse) ProtoMessage() {} + +func (x *AuthRegisterResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthRegisterResponse.ProtoReflect.Descriptor instead. +func (*AuthRegisterResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthRegisterResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type AuthApproveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AuthId string `protobuf:"bytes,1,opt,name=auth_id,json=authId,proto3" json:"auth_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthApproveRequest) Reset() { + *x = AuthApproveRequest{} + mi := &file_headscale_v1_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthApproveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthApproveRequest) ProtoMessage() {} + +func (x *AuthApproveRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthApproveRequest.ProtoReflect.Descriptor instead. +func (*AuthApproveRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *AuthApproveRequest) GetAuthId() string { + if x != nil { + return x.AuthId + } + return "" +} + +type AuthApproveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthApproveResponse) Reset() { + *x = AuthApproveResponse{} + mi := &file_headscale_v1_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthApproveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthApproveResponse) ProtoMessage() {} + +func (x *AuthApproveResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthApproveResponse.ProtoReflect.Descriptor instead. +func (*AuthApproveResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{3} +} + +type AuthRejectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AuthId string `protobuf:"bytes,1,opt,name=auth_id,json=authId,proto3" json:"auth_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthRejectRequest) Reset() { + *x = AuthRejectRequest{} + mi := &file_headscale_v1_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthRejectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthRejectRequest) ProtoMessage() {} + +func (x *AuthRejectRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthRejectRequest.ProtoReflect.Descriptor instead. +func (*AuthRejectRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{4} +} + +func (x *AuthRejectRequest) GetAuthId() string { + if x != nil { + return x.AuthId + } + return "" +} + +type AuthRejectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthRejectResponse) Reset() { + *x = AuthRejectResponse{} + mi := &file_headscale_v1_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthRejectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthRejectResponse) ProtoMessage() {} + +func (x *AuthRejectResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_auth_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthRejectResponse.ProtoReflect.Descriptor instead. +func (*AuthRejectResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_auth_proto_rawDescGZIP(), []int{5} +} + +var File_headscale_v1_auth_proto protoreflect.FileDescriptor + +const file_headscale_v1_auth_proto_rawDesc = "" + + "\n" + + "\x17headscale/v1/auth.proto\x12\fheadscale.v1\x1a\x17headscale/v1/node.proto\"B\n" + + "\x13AuthRegisterRequest\x12\x12\n" + + "\x04user\x18\x01 \x01(\tR\x04user\x12\x17\n" + + "\aauth_id\x18\x02 \x01(\tR\x06authId\">\n" + + "\x14AuthRegisterResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"-\n" + + "\x12AuthApproveRequest\x12\x17\n" + + "\aauth_id\x18\x01 \x01(\tR\x06authId\"\x15\n" + + "\x13AuthApproveResponse\",\n" + + "\x11AuthRejectRequest\x12\x17\n" + + "\aauth_id\x18\x01 \x01(\tR\x06authId\"\x14\n" + + "\x12AuthRejectResponseB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_auth_proto_rawDescOnce sync.Once + file_headscale_v1_auth_proto_rawDescData []byte +) + +func file_headscale_v1_auth_proto_rawDescGZIP() []byte { + file_headscale_v1_auth_proto_rawDescOnce.Do(func() { + file_headscale_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_auth_proto_rawDesc), len(file_headscale_v1_auth_proto_rawDesc))) + }) + return file_headscale_v1_auth_proto_rawDescData +} + +var file_headscale_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_headscale_v1_auth_proto_goTypes = []any{ + (*AuthRegisterRequest)(nil), // 0: headscale.v1.AuthRegisterRequest + (*AuthRegisterResponse)(nil), // 1: headscale.v1.AuthRegisterResponse + (*AuthApproveRequest)(nil), // 2: headscale.v1.AuthApproveRequest + (*AuthApproveResponse)(nil), // 3: headscale.v1.AuthApproveResponse + (*AuthRejectRequest)(nil), // 4: headscale.v1.AuthRejectRequest + (*AuthRejectResponse)(nil), // 5: headscale.v1.AuthRejectResponse + (*Node)(nil), // 6: headscale.v1.Node +} +var file_headscale_v1_auth_proto_depIdxs = []int32{ + 6, // 0: headscale.v1.AuthRegisterResponse.node:type_name -> headscale.v1.Node + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_headscale_v1_auth_proto_init() } +func file_headscale_v1_auth_proto_init() { + if File_headscale_v1_auth_proto != nil { + return + } + file_headscale_v1_node_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_auth_proto_rawDesc), len(file_headscale_v1_auth_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_auth_proto_goTypes, + DependencyIndexes: file_headscale_v1_auth_proto_depIdxs, + MessageInfos: file_headscale_v1_auth_proto_msgTypes, + }.Build() + File_headscale_v1_auth_proto = out.File + file_headscale_v1_auth_proto_goTypes = nil + file_headscale_v1_auth_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/device.pb.go b/gen/go/headscale/v1/device.pb.go new file mode 100644 index 000000000..e2362b05a --- /dev/null +++ b/gen/go/headscale/v1/device.pb.go @@ -0,0 +1,890 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/device.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Latency struct { + state protoimpl.MessageState `protogen:"open.v1"` + LatencyMs float32 `protobuf:"fixed32,1,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"` + Preferred bool `protobuf:"varint,2,opt,name=preferred,proto3" json:"preferred,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Latency) Reset() { + *x = Latency{} + mi := &file_headscale_v1_device_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Latency) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Latency) ProtoMessage() {} + +func (x *Latency) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Latency.ProtoReflect.Descriptor instead. +func (*Latency) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{0} +} + +func (x *Latency) GetLatencyMs() float32 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +func (x *Latency) GetPreferred() bool { + if x != nil { + return x.Preferred + } + return false +} + +type ClientSupports struct { + state protoimpl.MessageState `protogen:"open.v1"` + HairPinning bool `protobuf:"varint,1,opt,name=hair_pinning,json=hairPinning,proto3" json:"hair_pinning,omitempty"` + Ipv6 bool `protobuf:"varint,2,opt,name=ipv6,proto3" json:"ipv6,omitempty"` + Pcp bool `protobuf:"varint,3,opt,name=pcp,proto3" json:"pcp,omitempty"` + Pmp bool `protobuf:"varint,4,opt,name=pmp,proto3" json:"pmp,omitempty"` + Udp bool `protobuf:"varint,5,opt,name=udp,proto3" json:"udp,omitempty"` + Upnp bool `protobuf:"varint,6,opt,name=upnp,proto3" json:"upnp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientSupports) Reset() { + *x = ClientSupports{} + mi := &file_headscale_v1_device_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientSupports) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientSupports) ProtoMessage() {} + +func (x *ClientSupports) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientSupports.ProtoReflect.Descriptor instead. +func (*ClientSupports) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{1} +} + +func (x *ClientSupports) GetHairPinning() bool { + if x != nil { + return x.HairPinning + } + return false +} + +func (x *ClientSupports) GetIpv6() bool { + if x != nil { + return x.Ipv6 + } + return false +} + +func (x *ClientSupports) GetPcp() bool { + if x != nil { + return x.Pcp + } + return false +} + +func (x *ClientSupports) GetPmp() bool { + if x != nil { + return x.Pmp + } + return false +} + +func (x *ClientSupports) GetUdp() bool { + if x != nil { + return x.Udp + } + return false +} + +func (x *ClientSupports) GetUpnp() bool { + if x != nil { + return x.Upnp + } + return false +} + +type ClientConnectivity struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoints []string `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + Derp string `protobuf:"bytes,2,opt,name=derp,proto3" json:"derp,omitempty"` + MappingVariesByDestIp bool `protobuf:"varint,3,opt,name=mapping_varies_by_dest_ip,json=mappingVariesByDestIp,proto3" json:"mapping_varies_by_dest_ip,omitempty"` + Latency map[string]*Latency `protobuf:"bytes,4,rep,name=latency,proto3" json:"latency,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + ClientSupports *ClientSupports `protobuf:"bytes,5,opt,name=client_supports,json=clientSupports,proto3" json:"client_supports,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientConnectivity) Reset() { + *x = ClientConnectivity{} + mi := &file_headscale_v1_device_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientConnectivity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientConnectivity) ProtoMessage() {} + +func (x *ClientConnectivity) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientConnectivity.ProtoReflect.Descriptor instead. +func (*ClientConnectivity) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{2} +} + +func (x *ClientConnectivity) GetEndpoints() []string { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *ClientConnectivity) GetDerp() string { + if x != nil { + return x.Derp + } + return "" +} + +func (x *ClientConnectivity) GetMappingVariesByDestIp() bool { + if x != nil { + return x.MappingVariesByDestIp + } + return false +} + +func (x *ClientConnectivity) GetLatency() map[string]*Latency { + if x != nil { + return x.Latency + } + return nil +} + +func (x *ClientConnectivity) GetClientSupports() *ClientSupports { + if x != nil { + return x.ClientSupports + } + return nil +} + +type GetDeviceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDeviceRequest) Reset() { + *x = GetDeviceRequest{} + mi := &file_headscale_v1_device_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDeviceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDeviceRequest) ProtoMessage() {} + +func (x *GetDeviceRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDeviceRequest.ProtoReflect.Descriptor instead. +func (*GetDeviceRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{3} +} + +func (x *GetDeviceRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetDeviceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Addresses []string `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + User string `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Hostname string `protobuf:"bytes,5,opt,name=hostname,proto3" json:"hostname,omitempty"` + ClientVersion string `protobuf:"bytes,6,opt,name=client_version,json=clientVersion,proto3" json:"client_version,omitempty"` + UpdateAvailable bool `protobuf:"varint,7,opt,name=update_available,json=updateAvailable,proto3" json:"update_available,omitempty"` + Os string `protobuf:"bytes,8,opt,name=os,proto3" json:"os,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=created,proto3" json:"created,omitempty"` + LastSeen *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + KeyExpiryDisabled bool `protobuf:"varint,11,opt,name=key_expiry_disabled,json=keyExpiryDisabled,proto3" json:"key_expiry_disabled,omitempty"` + Expires *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expires,proto3" json:"expires,omitempty"` + Authorized bool `protobuf:"varint,13,opt,name=authorized,proto3" json:"authorized,omitempty"` + IsExternal bool `protobuf:"varint,14,opt,name=is_external,json=isExternal,proto3" json:"is_external,omitempty"` + MachineKey string `protobuf:"bytes,15,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"` + NodeKey string `protobuf:"bytes,16,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + BlocksIncomingConnections bool `protobuf:"varint,17,opt,name=blocks_incoming_connections,json=blocksIncomingConnections,proto3" json:"blocks_incoming_connections,omitempty"` + EnabledRoutes []string `protobuf:"bytes,18,rep,name=enabled_routes,json=enabledRoutes,proto3" json:"enabled_routes,omitempty"` + AdvertisedRoutes []string `protobuf:"bytes,19,rep,name=advertised_routes,json=advertisedRoutes,proto3" json:"advertised_routes,omitempty"` + ClientConnectivity *ClientConnectivity `protobuf:"bytes,20,opt,name=client_connectivity,json=clientConnectivity,proto3" json:"client_connectivity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDeviceResponse) Reset() { + *x = GetDeviceResponse{} + mi := &file_headscale_v1_device_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDeviceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDeviceResponse) ProtoMessage() {} + +func (x *GetDeviceResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDeviceResponse.ProtoReflect.Descriptor instead. +func (*GetDeviceResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{4} +} + +func (x *GetDeviceResponse) GetAddresses() []string { + if x != nil { + return x.Addresses + } + return nil +} + +func (x *GetDeviceResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetDeviceResponse) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *GetDeviceResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetDeviceResponse) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *GetDeviceResponse) GetClientVersion() string { + if x != nil { + return x.ClientVersion + } + return "" +} + +func (x *GetDeviceResponse) GetUpdateAvailable() bool { + if x != nil { + return x.UpdateAvailable + } + return false +} + +func (x *GetDeviceResponse) GetOs() string { + if x != nil { + return x.Os + } + return "" +} + +func (x *GetDeviceResponse) GetCreated() *timestamppb.Timestamp { + if x != nil { + return x.Created + } + return nil +} + +func (x *GetDeviceResponse) GetLastSeen() *timestamppb.Timestamp { + if x != nil { + return x.LastSeen + } + return nil +} + +func (x *GetDeviceResponse) GetKeyExpiryDisabled() bool { + if x != nil { + return x.KeyExpiryDisabled + } + return false +} + +func (x *GetDeviceResponse) GetExpires() *timestamppb.Timestamp { + if x != nil { + return x.Expires + } + return nil +} + +func (x *GetDeviceResponse) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +func (x *GetDeviceResponse) GetIsExternal() bool { + if x != nil { + return x.IsExternal + } + return false +} + +func (x *GetDeviceResponse) GetMachineKey() string { + if x != nil { + return x.MachineKey + } + return "" +} + +func (x *GetDeviceResponse) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *GetDeviceResponse) GetBlocksIncomingConnections() bool { + if x != nil { + return x.BlocksIncomingConnections + } + return false +} + +func (x *GetDeviceResponse) GetEnabledRoutes() []string { + if x != nil { + return x.EnabledRoutes + } + return nil +} + +func (x *GetDeviceResponse) GetAdvertisedRoutes() []string { + if x != nil { + return x.AdvertisedRoutes + } + return nil +} + +func (x *GetDeviceResponse) GetClientConnectivity() *ClientConnectivity { + if x != nil { + return x.ClientConnectivity + } + return nil +} + +type DeleteDeviceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteDeviceRequest) Reset() { + *x = DeleteDeviceRequest{} + mi := &file_headscale_v1_device_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteDeviceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteDeviceRequest) ProtoMessage() {} + +func (x *DeleteDeviceRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteDeviceRequest.ProtoReflect.Descriptor instead. +func (*DeleteDeviceRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteDeviceRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeleteDeviceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteDeviceResponse) Reset() { + *x = DeleteDeviceResponse{} + mi := &file_headscale_v1_device_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteDeviceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteDeviceResponse) ProtoMessage() {} + +func (x *DeleteDeviceResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteDeviceResponse.ProtoReflect.Descriptor instead. +func (*DeleteDeviceResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{6} +} + +type GetDeviceRoutesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDeviceRoutesRequest) Reset() { + *x = GetDeviceRoutesRequest{} + mi := &file_headscale_v1_device_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDeviceRoutesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDeviceRoutesRequest) ProtoMessage() {} + +func (x *GetDeviceRoutesRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDeviceRoutesRequest.ProtoReflect.Descriptor instead. +func (*GetDeviceRoutesRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{7} +} + +func (x *GetDeviceRoutesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetDeviceRoutesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + EnabledRoutes []string `protobuf:"bytes,1,rep,name=enabled_routes,json=enabledRoutes,proto3" json:"enabled_routes,omitempty"` + AdvertisedRoutes []string `protobuf:"bytes,2,rep,name=advertised_routes,json=advertisedRoutes,proto3" json:"advertised_routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDeviceRoutesResponse) Reset() { + *x = GetDeviceRoutesResponse{} + mi := &file_headscale_v1_device_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDeviceRoutesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDeviceRoutesResponse) ProtoMessage() {} + +func (x *GetDeviceRoutesResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDeviceRoutesResponse.ProtoReflect.Descriptor instead. +func (*GetDeviceRoutesResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{8} +} + +func (x *GetDeviceRoutesResponse) GetEnabledRoutes() []string { + if x != nil { + return x.EnabledRoutes + } + return nil +} + +func (x *GetDeviceRoutesResponse) GetAdvertisedRoutes() []string { + if x != nil { + return x.AdvertisedRoutes + } + return nil +} + +type EnableDeviceRoutesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Routes []string `protobuf:"bytes,2,rep,name=routes,proto3" json:"routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnableDeviceRoutesRequest) Reset() { + *x = EnableDeviceRoutesRequest{} + mi := &file_headscale_v1_device_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnableDeviceRoutesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnableDeviceRoutesRequest) ProtoMessage() {} + +func (x *EnableDeviceRoutesRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnableDeviceRoutesRequest.ProtoReflect.Descriptor instead. +func (*EnableDeviceRoutesRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{9} +} + +func (x *EnableDeviceRoutesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *EnableDeviceRoutesRequest) GetRoutes() []string { + if x != nil { + return x.Routes + } + return nil +} + +type EnableDeviceRoutesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + EnabledRoutes []string `protobuf:"bytes,1,rep,name=enabled_routes,json=enabledRoutes,proto3" json:"enabled_routes,omitempty"` + AdvertisedRoutes []string `protobuf:"bytes,2,rep,name=advertised_routes,json=advertisedRoutes,proto3" json:"advertised_routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnableDeviceRoutesResponse) Reset() { + *x = EnableDeviceRoutesResponse{} + mi := &file_headscale_v1_device_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnableDeviceRoutesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnableDeviceRoutesResponse) ProtoMessage() {} + +func (x *EnableDeviceRoutesResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_device_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnableDeviceRoutesResponse.ProtoReflect.Descriptor instead. +func (*EnableDeviceRoutesResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_device_proto_rawDescGZIP(), []int{10} +} + +func (x *EnableDeviceRoutesResponse) GetEnabledRoutes() []string { + if x != nil { + return x.EnabledRoutes + } + return nil +} + +func (x *EnableDeviceRoutesResponse) GetAdvertisedRoutes() []string { + if x != nil { + return x.AdvertisedRoutes + } + return nil +} + +var File_headscale_v1_device_proto protoreflect.FileDescriptor + +const file_headscale_v1_device_proto_rawDesc = "" + + "\n" + + "\x19headscale/v1/device.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"F\n" + + "\aLatency\x12\x1d\n" + + "\n" + + "latency_ms\x18\x01 \x01(\x02R\tlatencyMs\x12\x1c\n" + + "\tpreferred\x18\x02 \x01(\bR\tpreferred\"\x91\x01\n" + + "\x0eClientSupports\x12!\n" + + "\fhair_pinning\x18\x01 \x01(\bR\vhairPinning\x12\x12\n" + + "\x04ipv6\x18\x02 \x01(\bR\x04ipv6\x12\x10\n" + + "\x03pcp\x18\x03 \x01(\bR\x03pcp\x12\x10\n" + + "\x03pmp\x18\x04 \x01(\bR\x03pmp\x12\x10\n" + + "\x03udp\x18\x05 \x01(\bR\x03udp\x12\x12\n" + + "\x04upnp\x18\x06 \x01(\bR\x04upnp\"\xe3\x02\n" + + "\x12ClientConnectivity\x12\x1c\n" + + "\tendpoints\x18\x01 \x03(\tR\tendpoints\x12\x12\n" + + "\x04derp\x18\x02 \x01(\tR\x04derp\x128\n" + + "\x19mapping_varies_by_dest_ip\x18\x03 \x01(\bR\x15mappingVariesByDestIp\x12G\n" + + "\alatency\x18\x04 \x03(\v2-.headscale.v1.ClientConnectivity.LatencyEntryR\alatency\x12E\n" + + "\x0fclient_supports\x18\x05 \x01(\v2\x1c.headscale.v1.ClientSupportsR\x0eclientSupports\x1aQ\n" + + "\fLatencyEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12+\n" + + "\x05value\x18\x02 \x01(\v2\x15.headscale.v1.LatencyR\x05value:\x028\x01\"\"\n" + + "\x10GetDeviceRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\xa0\x06\n" + + "\x11GetDeviceResponse\x12\x1c\n" + + "\taddresses\x18\x01 \x03(\tR\taddresses\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" + + "\x04user\x18\x03 \x01(\tR\x04user\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x12\x1a\n" + + "\bhostname\x18\x05 \x01(\tR\bhostname\x12%\n" + + "\x0eclient_version\x18\x06 \x01(\tR\rclientVersion\x12)\n" + + "\x10update_available\x18\a \x01(\bR\x0fupdateAvailable\x12\x0e\n" + + "\x02os\x18\b \x01(\tR\x02os\x124\n" + + "\acreated\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\acreated\x127\n" + + "\tlast_seen\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\blastSeen\x12.\n" + + "\x13key_expiry_disabled\x18\v \x01(\bR\x11keyExpiryDisabled\x124\n" + + "\aexpires\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\aexpires\x12\x1e\n" + + "\n" + + "authorized\x18\r \x01(\bR\n" + + "authorized\x12\x1f\n" + + "\vis_external\x18\x0e \x01(\bR\n" + + "isExternal\x12\x1f\n" + + "\vmachine_key\x18\x0f \x01(\tR\n" + + "machineKey\x12\x19\n" + + "\bnode_key\x18\x10 \x01(\tR\anodeKey\x12>\n" + + "\x1bblocks_incoming_connections\x18\x11 \x01(\bR\x19blocksIncomingConnections\x12%\n" + + "\x0eenabled_routes\x18\x12 \x03(\tR\renabledRoutes\x12+\n" + + "\x11advertised_routes\x18\x13 \x03(\tR\x10advertisedRoutes\x12Q\n" + + "\x13client_connectivity\x18\x14 \x01(\v2 .headscale.v1.ClientConnectivityR\x12clientConnectivity\"%\n" + + "\x13DeleteDeviceRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x16\n" + + "\x14DeleteDeviceResponse\"(\n" + + "\x16GetDeviceRoutesRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"m\n" + + "\x17GetDeviceRoutesResponse\x12%\n" + + "\x0eenabled_routes\x18\x01 \x03(\tR\renabledRoutes\x12+\n" + + "\x11advertised_routes\x18\x02 \x03(\tR\x10advertisedRoutes\"C\n" + + "\x19EnableDeviceRoutesRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + + "\x06routes\x18\x02 \x03(\tR\x06routes\"p\n" + + "\x1aEnableDeviceRoutesResponse\x12%\n" + + "\x0eenabled_routes\x18\x01 \x03(\tR\renabledRoutes\x12+\n" + + "\x11advertised_routes\x18\x02 \x03(\tR\x10advertisedRoutesB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_device_proto_rawDescOnce sync.Once + file_headscale_v1_device_proto_rawDescData []byte +) + +func file_headscale_v1_device_proto_rawDescGZIP() []byte { + file_headscale_v1_device_proto_rawDescOnce.Do(func() { + file_headscale_v1_device_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_device_proto_rawDesc), len(file_headscale_v1_device_proto_rawDesc))) + }) + return file_headscale_v1_device_proto_rawDescData +} + +var file_headscale_v1_device_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_headscale_v1_device_proto_goTypes = []any{ + (*Latency)(nil), // 0: headscale.v1.Latency + (*ClientSupports)(nil), // 1: headscale.v1.ClientSupports + (*ClientConnectivity)(nil), // 2: headscale.v1.ClientConnectivity + (*GetDeviceRequest)(nil), // 3: headscale.v1.GetDeviceRequest + (*GetDeviceResponse)(nil), // 4: headscale.v1.GetDeviceResponse + (*DeleteDeviceRequest)(nil), // 5: headscale.v1.DeleteDeviceRequest + (*DeleteDeviceResponse)(nil), // 6: headscale.v1.DeleteDeviceResponse + (*GetDeviceRoutesRequest)(nil), // 7: headscale.v1.GetDeviceRoutesRequest + (*GetDeviceRoutesResponse)(nil), // 8: headscale.v1.GetDeviceRoutesResponse + (*EnableDeviceRoutesRequest)(nil), // 9: headscale.v1.EnableDeviceRoutesRequest + (*EnableDeviceRoutesResponse)(nil), // 10: headscale.v1.EnableDeviceRoutesResponse + nil, // 11: headscale.v1.ClientConnectivity.LatencyEntry + (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp +} +var file_headscale_v1_device_proto_depIdxs = []int32{ + 11, // 0: headscale.v1.ClientConnectivity.latency:type_name -> headscale.v1.ClientConnectivity.LatencyEntry + 1, // 1: headscale.v1.ClientConnectivity.client_supports:type_name -> headscale.v1.ClientSupports + 12, // 2: headscale.v1.GetDeviceResponse.created:type_name -> google.protobuf.Timestamp + 12, // 3: headscale.v1.GetDeviceResponse.last_seen:type_name -> google.protobuf.Timestamp + 12, // 4: headscale.v1.GetDeviceResponse.expires:type_name -> google.protobuf.Timestamp + 2, // 5: headscale.v1.GetDeviceResponse.client_connectivity:type_name -> headscale.v1.ClientConnectivity + 0, // 6: headscale.v1.ClientConnectivity.LatencyEntry.value:type_name -> headscale.v1.Latency + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_headscale_v1_device_proto_init() } +func file_headscale_v1_device_proto_init() { + if File_headscale_v1_device_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_device_proto_rawDesc), len(file_headscale_v1_device_proto_rawDesc)), + NumEnums: 0, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_device_proto_goTypes, + DependencyIndexes: file_headscale_v1_device_proto_depIdxs, + MessageInfos: file_headscale_v1_device_proto_msgTypes, + }.Build() + File_headscale_v1_device_proto = out.File + file_headscale_v1_device_proto_goTypes = nil + file_headscale_v1_device_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go new file mode 100644 index 000000000..4a1875126 --- /dev/null +++ b/gen/go/headscale/v1/headscale.pb.go @@ -0,0 +1,318 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/headscale.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HealthRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthRequest) Reset() { + *x = HealthRequest{} + mi := &file_headscale_v1_headscale_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthRequest) ProtoMessage() {} + +func (x *HealthRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_headscale_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthRequest.ProtoReflect.Descriptor instead. +func (*HealthRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{0} +} + +type HealthResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + DatabaseConnectivity bool `protobuf:"varint,1,opt,name=database_connectivity,json=databaseConnectivity,proto3" json:"database_connectivity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HealthResponse) Reset() { + *x = HealthResponse{} + mi := &file_headscale_v1_headscale_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HealthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthResponse) ProtoMessage() {} + +func (x *HealthResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_headscale_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthResponse.ProtoReflect.Descriptor instead. +func (*HealthResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_headscale_proto_rawDescGZIP(), []int{1} +} + +func (x *HealthResponse) GetDatabaseConnectivity() bool { + if x != nil { + return x.DatabaseConnectivity + } + return false +} + +var File_headscale_v1_headscale_proto protoreflect.FileDescriptor + +const file_headscale_v1_headscale_proto_rawDesc = "" + + "\n" + + "\x1cheadscale/v1/headscale.proto\x12\fheadscale.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17headscale/v1/user.proto\x1a\x1dheadscale/v1/preauthkey.proto\x1a\x17headscale/v1/node.proto\x1a\x19headscale/v1/apikey.proto\x1a\x17headscale/v1/auth.proto\x1a\x19headscale/v1/policy.proto\"\x0f\n" + + "\rHealthRequest\"E\n" + + "\x0eHealthResponse\x123\n" + + "\x15database_connectivity\x18\x01 \x01(\bR\x14databaseConnectivity2\xe0\x1a\n" + + "\x10HeadscaleService\x12h\n" + + "\n" + + "CreateUser\x12\x1f.headscale.v1.CreateUserRequest\x1a .headscale.v1.CreateUserResponse\"\x17\x82\xd3\xe4\x93\x02\x11:\x01*\"\f/api/v1/user\x12\x80\x01\n" + + "\n" + + "RenameUser\x12\x1f.headscale.v1.RenameUserRequest\x1a .headscale.v1.RenameUserResponse\"/\x82\xd3\xe4\x93\x02)\"'/api/v1/user/{old_id}/rename/{new_name}\x12j\n" + + "\n" + + "DeleteUser\x12\x1f.headscale.v1.DeleteUserRequest\x1a .headscale.v1.DeleteUserResponse\"\x19\x82\xd3\xe4\x93\x02\x13*\x11/api/v1/user/{id}\x12b\n" + + "\tListUsers\x12\x1e.headscale.v1.ListUsersRequest\x1a\x1f.headscale.v1.ListUsersResponse\"\x14\x82\xd3\xe4\x93\x02\x0e\x12\f/api/v1/user\x12\x80\x01\n" + + "\x10CreatePreAuthKey\x12%.headscale.v1.CreatePreAuthKeyRequest\x1a&.headscale.v1.CreatePreAuthKeyResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/api/v1/preauthkey\x12\x87\x01\n" + + "\x10ExpirePreAuthKey\x12%.headscale.v1.ExpirePreAuthKeyRequest\x1a&.headscale.v1.ExpirePreAuthKeyResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/api/v1/preauthkey/expire\x12}\n" + + "\x10DeletePreAuthKey\x12%.headscale.v1.DeletePreAuthKeyRequest\x1a&.headscale.v1.DeletePreAuthKeyResponse\"\x1a\x82\xd3\xe4\x93\x02\x14*\x12/api/v1/preauthkey\x12z\n" + + "\x0fListPreAuthKeys\x12$.headscale.v1.ListPreAuthKeysRequest\x1a%.headscale.v1.ListPreAuthKeysResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/preauthkey\x12}\n" + + "\x0fDebugCreateNode\x12$.headscale.v1.DebugCreateNodeRequest\x1a%.headscale.v1.DebugCreateNodeResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/api/v1/debug/node\x12f\n" + + "\aGetNode\x12\x1c.headscale.v1.GetNodeRequest\x1a\x1d.headscale.v1.GetNodeResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/node/{node_id}\x12n\n" + + "\aSetTags\x12\x1c.headscale.v1.SetTagsRequest\x1a\x1d.headscale.v1.SetTagsResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/v1/node/{node_id}/tags\x12\x96\x01\n" + + "\x11SetApprovedRoutes\x12&.headscale.v1.SetApprovedRoutesRequest\x1a'.headscale.v1.SetApprovedRoutesResponse\"0\x82\xd3\xe4\x93\x02*:\x01*\"%/api/v1/node/{node_id}/approve_routes\x12t\n" + + "\fRegisterNode\x12!.headscale.v1.RegisterNodeRequest\x1a\".headscale.v1.RegisterNodeResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x15/api/v1/node/register\x12o\n" + + "\n" + + "DeleteNode\x12\x1f.headscale.v1.DeleteNodeRequest\x1a .headscale.v1.DeleteNodeResponse\"\x1e\x82\xd3\xe4\x93\x02\x18*\x16/api/v1/node/{node_id}\x12v\n" + + "\n" + + "ExpireNode\x12\x1f.headscale.v1.ExpireNodeRequest\x1a .headscale.v1.ExpireNodeResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1d/api/v1/node/{node_id}/expire\x12\x81\x01\n" + + "\n" + + "RenameNode\x12\x1f.headscale.v1.RenameNodeRequest\x1a .headscale.v1.RenameNodeResponse\"0\x82\xd3\xe4\x93\x02*\"(/api/v1/node/{node_id}/rename/{new_name}\x12b\n" + + "\tListNodes\x12\x1e.headscale.v1.ListNodesRequest\x1a\x1f.headscale.v1.ListNodesResponse\"\x14\x82\xd3\xe4\x93\x02\x0e\x12\f/api/v1/node\x12\x80\x01\n" + + "\x0fBackfillNodeIPs\x12$.headscale.v1.BackfillNodeIPsRequest\x1a%.headscale.v1.BackfillNodeIPsResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x18/api/v1/node/backfillips\x12w\n" + + "\fAuthRegister\x12!.headscale.v1.AuthRegisterRequest\x1a\".headscale.v1.AuthRegisterResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/auth/register\x12s\n" + + "\vAuthApprove\x12 .headscale.v1.AuthApproveRequest\x1a!.headscale.v1.AuthApproveResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/auth/approve\x12o\n" + + "\n" + + "AuthReject\x12\x1f.headscale.v1.AuthRejectRequest\x1a .headscale.v1.AuthRejectResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/api/v1/auth/reject\x12p\n" + + "\fCreateApiKey\x12!.headscale.v1.CreateApiKeyRequest\x1a\".headscale.v1.CreateApiKeyResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\"\x0e/api/v1/apikey\x12w\n" + + "\fExpireApiKey\x12!.headscale.v1.ExpireApiKeyRequest\x1a\".headscale.v1.ExpireApiKeyResponse\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/api/v1/apikey/expire\x12j\n" + + "\vListApiKeys\x12 .headscale.v1.ListApiKeysRequest\x1a!.headscale.v1.ListApiKeysResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/apikey\x12v\n" + + "\fDeleteApiKey\x12!.headscale.v1.DeleteApiKeyRequest\x1a\".headscale.v1.DeleteApiKeyResponse\"\x1f\x82\xd3\xe4\x93\x02\x19*\x17/api/v1/apikey/{prefix}\x12d\n" + + "\tGetPolicy\x12\x1e.headscale.v1.GetPolicyRequest\x1a\x1f.headscale.v1.GetPolicyResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/policy\x12g\n" + + "\tSetPolicy\x12\x1e.headscale.v1.SetPolicyRequest\x1a\x1f.headscale.v1.SetPolicyResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\x1a\x0e/api/v1/policy\x12s\n" + + "\vCheckPolicy\x12 .headscale.v1.CheckPolicyRequest\x1a!.headscale.v1.CheckPolicyResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/policy/check\x12[\n" + + "\x06Health\x12\x1b.headscale.v1.HealthRequest\x1a\x1c.headscale.v1.HealthResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/api/v1/healthB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_headscale_proto_rawDescOnce sync.Once + file_headscale_v1_headscale_proto_rawDescData []byte +) + +func file_headscale_v1_headscale_proto_rawDescGZIP() []byte { + file_headscale_v1_headscale_proto_rawDescOnce.Do(func() { + file_headscale_v1_headscale_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_headscale_proto_rawDesc), len(file_headscale_v1_headscale_proto_rawDesc))) + }) + return file_headscale_v1_headscale_proto_rawDescData +} + +var file_headscale_v1_headscale_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_headscale_v1_headscale_proto_goTypes = []any{ + (*HealthRequest)(nil), // 0: headscale.v1.HealthRequest + (*HealthResponse)(nil), // 1: headscale.v1.HealthResponse + (*CreateUserRequest)(nil), // 2: headscale.v1.CreateUserRequest + (*RenameUserRequest)(nil), // 3: headscale.v1.RenameUserRequest + (*DeleteUserRequest)(nil), // 4: headscale.v1.DeleteUserRequest + (*ListUsersRequest)(nil), // 5: headscale.v1.ListUsersRequest + (*CreatePreAuthKeyRequest)(nil), // 6: headscale.v1.CreatePreAuthKeyRequest + (*ExpirePreAuthKeyRequest)(nil), // 7: headscale.v1.ExpirePreAuthKeyRequest + (*DeletePreAuthKeyRequest)(nil), // 8: headscale.v1.DeletePreAuthKeyRequest + (*ListPreAuthKeysRequest)(nil), // 9: headscale.v1.ListPreAuthKeysRequest + (*DebugCreateNodeRequest)(nil), // 10: headscale.v1.DebugCreateNodeRequest + (*GetNodeRequest)(nil), // 11: headscale.v1.GetNodeRequest + (*SetTagsRequest)(nil), // 12: headscale.v1.SetTagsRequest + (*SetApprovedRoutesRequest)(nil), // 13: headscale.v1.SetApprovedRoutesRequest + (*RegisterNodeRequest)(nil), // 14: headscale.v1.RegisterNodeRequest + (*DeleteNodeRequest)(nil), // 15: headscale.v1.DeleteNodeRequest + (*ExpireNodeRequest)(nil), // 16: headscale.v1.ExpireNodeRequest + (*RenameNodeRequest)(nil), // 17: headscale.v1.RenameNodeRequest + (*ListNodesRequest)(nil), // 18: headscale.v1.ListNodesRequest + (*BackfillNodeIPsRequest)(nil), // 19: headscale.v1.BackfillNodeIPsRequest + (*AuthRegisterRequest)(nil), // 20: headscale.v1.AuthRegisterRequest + (*AuthApproveRequest)(nil), // 21: headscale.v1.AuthApproveRequest + (*AuthRejectRequest)(nil), // 22: headscale.v1.AuthRejectRequest + (*CreateApiKeyRequest)(nil), // 23: headscale.v1.CreateApiKeyRequest + (*ExpireApiKeyRequest)(nil), // 24: headscale.v1.ExpireApiKeyRequest + (*ListApiKeysRequest)(nil), // 25: headscale.v1.ListApiKeysRequest + (*DeleteApiKeyRequest)(nil), // 26: headscale.v1.DeleteApiKeyRequest + (*GetPolicyRequest)(nil), // 27: headscale.v1.GetPolicyRequest + (*SetPolicyRequest)(nil), // 28: headscale.v1.SetPolicyRequest + (*CheckPolicyRequest)(nil), // 29: headscale.v1.CheckPolicyRequest + (*CreateUserResponse)(nil), // 30: headscale.v1.CreateUserResponse + (*RenameUserResponse)(nil), // 31: headscale.v1.RenameUserResponse + (*DeleteUserResponse)(nil), // 32: headscale.v1.DeleteUserResponse + (*ListUsersResponse)(nil), // 33: headscale.v1.ListUsersResponse + (*CreatePreAuthKeyResponse)(nil), // 34: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyResponse)(nil), // 35: headscale.v1.ExpirePreAuthKeyResponse + (*DeletePreAuthKeyResponse)(nil), // 36: headscale.v1.DeletePreAuthKeyResponse + (*ListPreAuthKeysResponse)(nil), // 37: headscale.v1.ListPreAuthKeysResponse + (*DebugCreateNodeResponse)(nil), // 38: headscale.v1.DebugCreateNodeResponse + (*GetNodeResponse)(nil), // 39: headscale.v1.GetNodeResponse + (*SetTagsResponse)(nil), // 40: headscale.v1.SetTagsResponse + (*SetApprovedRoutesResponse)(nil), // 41: headscale.v1.SetApprovedRoutesResponse + (*RegisterNodeResponse)(nil), // 42: headscale.v1.RegisterNodeResponse + (*DeleteNodeResponse)(nil), // 43: headscale.v1.DeleteNodeResponse + (*ExpireNodeResponse)(nil), // 44: headscale.v1.ExpireNodeResponse + (*RenameNodeResponse)(nil), // 45: headscale.v1.RenameNodeResponse + (*ListNodesResponse)(nil), // 46: headscale.v1.ListNodesResponse + (*BackfillNodeIPsResponse)(nil), // 47: headscale.v1.BackfillNodeIPsResponse + (*AuthRegisterResponse)(nil), // 48: headscale.v1.AuthRegisterResponse + (*AuthApproveResponse)(nil), // 49: headscale.v1.AuthApproveResponse + (*AuthRejectResponse)(nil), // 50: headscale.v1.AuthRejectResponse + (*CreateApiKeyResponse)(nil), // 51: headscale.v1.CreateApiKeyResponse + (*ExpireApiKeyResponse)(nil), // 52: headscale.v1.ExpireApiKeyResponse + (*ListApiKeysResponse)(nil), // 53: headscale.v1.ListApiKeysResponse + (*DeleteApiKeyResponse)(nil), // 54: headscale.v1.DeleteApiKeyResponse + (*GetPolicyResponse)(nil), // 55: headscale.v1.GetPolicyResponse + (*SetPolicyResponse)(nil), // 56: headscale.v1.SetPolicyResponse + (*CheckPolicyResponse)(nil), // 57: headscale.v1.CheckPolicyResponse +} +var file_headscale_v1_headscale_proto_depIdxs = []int32{ + 2, // 0: headscale.v1.HeadscaleService.CreateUser:input_type -> headscale.v1.CreateUserRequest + 3, // 1: headscale.v1.HeadscaleService.RenameUser:input_type -> headscale.v1.RenameUserRequest + 4, // 2: headscale.v1.HeadscaleService.DeleteUser:input_type -> headscale.v1.DeleteUserRequest + 5, // 3: headscale.v1.HeadscaleService.ListUsers:input_type -> headscale.v1.ListUsersRequest + 6, // 4: headscale.v1.HeadscaleService.CreatePreAuthKey:input_type -> headscale.v1.CreatePreAuthKeyRequest + 7, // 5: headscale.v1.HeadscaleService.ExpirePreAuthKey:input_type -> headscale.v1.ExpirePreAuthKeyRequest + 8, // 6: headscale.v1.HeadscaleService.DeletePreAuthKey:input_type -> headscale.v1.DeletePreAuthKeyRequest + 9, // 7: headscale.v1.HeadscaleService.ListPreAuthKeys:input_type -> headscale.v1.ListPreAuthKeysRequest + 10, // 8: headscale.v1.HeadscaleService.DebugCreateNode:input_type -> headscale.v1.DebugCreateNodeRequest + 11, // 9: headscale.v1.HeadscaleService.GetNode:input_type -> headscale.v1.GetNodeRequest + 12, // 10: headscale.v1.HeadscaleService.SetTags:input_type -> headscale.v1.SetTagsRequest + 13, // 11: headscale.v1.HeadscaleService.SetApprovedRoutes:input_type -> headscale.v1.SetApprovedRoutesRequest + 14, // 12: headscale.v1.HeadscaleService.RegisterNode:input_type -> headscale.v1.RegisterNodeRequest + 15, // 13: headscale.v1.HeadscaleService.DeleteNode:input_type -> headscale.v1.DeleteNodeRequest + 16, // 14: headscale.v1.HeadscaleService.ExpireNode:input_type -> headscale.v1.ExpireNodeRequest + 17, // 15: headscale.v1.HeadscaleService.RenameNode:input_type -> headscale.v1.RenameNodeRequest + 18, // 16: headscale.v1.HeadscaleService.ListNodes:input_type -> headscale.v1.ListNodesRequest + 19, // 17: headscale.v1.HeadscaleService.BackfillNodeIPs:input_type -> headscale.v1.BackfillNodeIPsRequest + 20, // 18: headscale.v1.HeadscaleService.AuthRegister:input_type -> headscale.v1.AuthRegisterRequest + 21, // 19: headscale.v1.HeadscaleService.AuthApprove:input_type -> headscale.v1.AuthApproveRequest + 22, // 20: headscale.v1.HeadscaleService.AuthReject:input_type -> headscale.v1.AuthRejectRequest + 23, // 21: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest + 24, // 22: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest + 25, // 23: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest + 26, // 24: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest + 27, // 25: headscale.v1.HeadscaleService.GetPolicy:input_type -> headscale.v1.GetPolicyRequest + 28, // 26: headscale.v1.HeadscaleService.SetPolicy:input_type -> headscale.v1.SetPolicyRequest + 29, // 27: headscale.v1.HeadscaleService.CheckPolicy:input_type -> headscale.v1.CheckPolicyRequest + 0, // 28: headscale.v1.HeadscaleService.Health:input_type -> headscale.v1.HealthRequest + 30, // 29: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse + 31, // 30: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse + 32, // 31: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse + 33, // 32: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse + 34, // 33: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse + 35, // 34: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse + 36, // 35: headscale.v1.HeadscaleService.DeletePreAuthKey:output_type -> headscale.v1.DeletePreAuthKeyResponse + 37, // 36: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse + 38, // 37: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse + 39, // 38: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse + 40, // 39: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse + 41, // 40: headscale.v1.HeadscaleService.SetApprovedRoutes:output_type -> headscale.v1.SetApprovedRoutesResponse + 42, // 41: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse + 43, // 42: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse + 44, // 43: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse + 45, // 44: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse + 46, // 45: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse + 47, // 46: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse + 48, // 47: headscale.v1.HeadscaleService.AuthRegister:output_type -> headscale.v1.AuthRegisterResponse + 49, // 48: headscale.v1.HeadscaleService.AuthApprove:output_type -> headscale.v1.AuthApproveResponse + 50, // 49: headscale.v1.HeadscaleService.AuthReject:output_type -> headscale.v1.AuthRejectResponse + 51, // 50: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse + 52, // 51: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse + 53, // 52: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse + 54, // 53: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse + 55, // 54: headscale.v1.HeadscaleService.GetPolicy:output_type -> headscale.v1.GetPolicyResponse + 56, // 55: headscale.v1.HeadscaleService.SetPolicy:output_type -> headscale.v1.SetPolicyResponse + 57, // 56: headscale.v1.HeadscaleService.CheckPolicy:output_type -> headscale.v1.CheckPolicyResponse + 1, // 57: headscale.v1.HeadscaleService.Health:output_type -> headscale.v1.HealthResponse + 29, // [29:58] is the sub-list for method output_type + 0, // [0:29] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_headscale_v1_headscale_proto_init() } +func file_headscale_v1_headscale_proto_init() { + if File_headscale_v1_headscale_proto != nil { + return + } + file_headscale_v1_user_proto_init() + file_headscale_v1_preauthkey_proto_init() + file_headscale_v1_node_proto_init() + file_headscale_v1_apikey_proto_init() + file_headscale_v1_auth_proto_init() + file_headscale_v1_policy_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_headscale_proto_rawDesc), len(file_headscale_v1_headscale_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_headscale_v1_headscale_proto_goTypes, + DependencyIndexes: file_headscale_v1_headscale_proto_depIdxs, + MessageInfos: file_headscale_v1_headscale_proto_msgTypes, + }.Build() + File_headscale_v1_headscale_proto = out.File + file_headscale_v1_headscale_proto_goTypes = nil + file_headscale_v1_headscale_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/headscale.pb.gw.go b/gen/go/headscale/v1/headscale.pb.gw.go new file mode 100644 index 000000000..cbe1c9c31 --- /dev/null +++ b/gen/go/headscale/v1/headscale.pb.gw.go @@ -0,0 +1,2201 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: headscale/v1/headscale.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_HeadscaleService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateUserRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CreateUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateUserRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreateUser(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_RenameUser_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RenameUserRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["old_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "old_id") + } + protoReq.OldId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "old_id", err) + } + val, ok = pathParams["new_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "new_name") + } + protoReq.NewName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "new_name", err) + } + msg, err := client.RenameUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_RenameUser_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RenameUserRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["old_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "old_id") + } + protoReq.OldId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "old_id", err) + } + val, ok = pathParams["new_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "new_name") + } + protoReq.NewName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "new_name", err) + } + msg, err := server.RenameUser(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteUserRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := client.DeleteUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteUserRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + protoReq.Id, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + msg, err := server.DeleteUser(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_ListUsers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_HeadscaleService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListUsersRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ListUsers_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListUsersRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ListUsers_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListUsers(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_CreatePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreatePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CreatePreAuthKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_CreatePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreatePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreatePreAuthKey(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_ExpirePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpirePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ExpirePreAuthKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ExpirePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpirePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ExpirePreAuthKey(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_DeletePreAuthKey_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_HeadscaleService_DeletePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeletePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeletePreAuthKey_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.DeletePreAuthKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_DeletePreAuthKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeletePreAuthKeyRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeletePreAuthKey_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.DeletePreAuthKey(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_ListPreAuthKeys_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListPreAuthKeysRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ListPreAuthKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ListPreAuthKeys_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListPreAuthKeysRequest + metadata runtime.ServerMetadata + ) + msg, err := server.ListPreAuthKeys(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_DebugCreateNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DebugCreateNodeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.DebugCreateNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_DebugCreateNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DebugCreateNodeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.DebugCreateNode(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_GetNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetNodeRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := client.GetNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_GetNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetNodeRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := server.GetNode(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_SetTags_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetTagsRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := client.SetTags(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_SetTags_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetTagsRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := server.SetTags(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_SetApprovedRoutes_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetApprovedRoutesRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := client.SetApprovedRoutes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_SetApprovedRoutes_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetApprovedRoutesRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := server.SetApprovedRoutes(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_RegisterNode_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_HeadscaleService_RegisterNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RegisterNodeRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_RegisterNode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.RegisterNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_RegisterNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RegisterNodeRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_RegisterNode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.RegisterNode(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_DeleteNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteNodeRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := client.DeleteNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_DeleteNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteNodeRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + msg, err := server.DeleteNode(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_ExpireNode_0 = &utilities.DoubleArray{Encoding: map[string]int{"node_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + +func request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpireNodeRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ExpireNode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ExpireNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ExpireNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpireNodeRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ExpireNode_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ExpireNode(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_RenameNode_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RenameNodeRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + val, ok = pathParams["new_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "new_name") + } + protoReq.NewName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "new_name", err) + } + msg, err := client.RenameNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_RenameNode_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq RenameNodeRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["node_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "node_id") + } + protoReq.NodeId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "node_id", err) + } + val, ok = pathParams["new_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "new_name") + } + protoReq.NewName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "new_name", err) + } + msg, err := server.RenameNode(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_ListNodes_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_HeadscaleService_ListNodes_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListNodesRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ListNodes_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListNodes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ListNodes_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListNodesRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_ListNodes_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListNodes(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_BackfillNodeIPs_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_HeadscaleService_BackfillNodeIPs_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BackfillNodeIPsRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_BackfillNodeIPs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.BackfillNodeIPs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_BackfillNodeIPs_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BackfillNodeIPsRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_BackfillNodeIPs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.BackfillNodeIPs(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_AuthRegister_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthRegisterRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.AuthRegister(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_AuthRegister_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthRegisterRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.AuthRegister(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_AuthApprove_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthApproveRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.AuthApprove(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_AuthApprove_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthApproveRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.AuthApprove(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_AuthReject_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthRejectRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.AuthReject(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_AuthReject_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AuthRejectRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.AuthReject(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateApiKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CreateApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateApiKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreateApiKey(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpireApiKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ExpireApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExpireApiKeyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ExpireApiKey(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListApiKeysRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ListApiKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListApiKeysRequest + metadata runtime.ServerMetadata + ) + msg, err := server.ListApiKeys(ctx, &protoReq) + return msg, metadata, err +} + +var filter_HeadscaleService_DeleteApiKey_0 = &utilities.DoubleArray{Encoding: map[string]int{"prefix": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + +func request_HeadscaleService_DeleteApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteApiKeyRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["prefix"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "prefix") + } + protoReq.Prefix, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "prefix", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeleteApiKey_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.DeleteApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_DeleteApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteApiKeyRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["prefix"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "prefix") + } + protoReq.Prefix, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "prefix", err) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HeadscaleService_DeleteApiKey_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.DeleteApiKey(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_GetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetPolicyRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.GetPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_GetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetPolicyRequest + metadata runtime.ServerMetadata + ) + msg, err := server.GetPolicy(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_SetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetPolicyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.SetPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_SetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq SetPolicyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.SetPolicy(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_CheckPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CheckPolicyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CheckPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_CheckPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CheckPolicyRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CheckPolicy(ctx, &protoReq) + return msg, metadata, err +} + +func request_HeadscaleService_Health_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq HealthRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Health(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_HeadscaleService_Health_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq HealthRequest + metadata runtime.ServerMetadata + ) + msg, err := server.Health(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux". +// UnaryRPC :call HeadscaleServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterHeadscaleServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server HeadscaleServiceServer) error { + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateUser", runtime.WithHTTPPathPattern("/api/v1/user")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_CreateUser_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreateUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RenameUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RenameUser", runtime.WithHTTPPathPattern("/api/v1/user/{old_id}/rename/{new_name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_RenameUser_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RenameUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteUser", runtime.WithHTTPPathPattern("/api/v1/user/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_DeleteUser_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListUsers", runtime.WithHTTPPathPattern("/api/v1/user")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ListUsers_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreatePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreatePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_CreatePreAuthKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreatePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpirePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpirePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ExpirePreAuthKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpirePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeletePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeletePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_DeletePreAuthKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeletePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListPreAuthKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListPreAuthKeys", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ListPreAuthKeys_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListPreAuthKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_DebugCreateNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DebugCreateNode", runtime.WithHTTPPathPattern("/api/v1/debug/node")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_DebugCreateNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DebugCreateNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_GetNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_GetNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_GetNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_SetTags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetTags", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/tags")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_SetTags_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetTags_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_SetApprovedRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetApprovedRoutes", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/approve_routes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_SetApprovedRoutes_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetApprovedRoutes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RegisterNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RegisterNode", runtime.WithHTTPPathPattern("/api/v1/node/register")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_RegisterNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RegisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_DeleteNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpireNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ExpireNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpireNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RenameNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RenameNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/rename/{new_name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_RenameNode_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RenameNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListNodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListNodes", runtime.WithHTTPPathPattern("/api/v1/node")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ListNodes_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListNodes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_BackfillNodeIPs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/BackfillNodeIPs", runtime.WithHTTPPathPattern("/api/v1/node/backfillips")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_BackfillNodeIPs_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_BackfillNodeIPs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthRegister_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthRegister", runtime.WithHTTPPathPattern("/api/v1/auth/register")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_AuthRegister_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthRegister_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthApprove_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthApprove", runtime.WithHTTPPathPattern("/api/v1/auth/approve")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_AuthApprove_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthApprove_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthReject_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthReject", runtime.WithHTTPPathPattern("/api/v1/auth/reject")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_AuthReject_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthReject_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreateApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_CreateApiKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreateApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpireApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ExpireApiKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpireApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListApiKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_ListApiKeys_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListApiKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/{prefix}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_DeleteApiKey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_GetPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_GetPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_GetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_HeadscaleService_SetPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_SetPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CheckPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CheckPolicy", runtime.WithHTTPPathPattern("/api/v1/policy/check")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_CheckPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CheckPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/Health", runtime.WithHTTPPathPattern("/api/v1/health")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_Health_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_Health_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterHeadscaleServiceHandlerFromEndpoint is same as RegisterHeadscaleServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterHeadscaleServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterHeadscaleServiceHandler(ctx, mux, conn) +} + +// RegisterHeadscaleServiceHandler registers the http handlers for service HeadscaleService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterHeadscaleServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterHeadscaleServiceHandlerClient(ctx, mux, NewHeadscaleServiceClient(conn)) +} + +// RegisterHeadscaleServiceHandlerClient registers the http handlers for service HeadscaleService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "HeadscaleServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "HeadscaleServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "HeadscaleServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client HeadscaleServiceClient) error { + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateUser", runtime.WithHTTPPathPattern("/api/v1/user")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_CreateUser_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreateUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RenameUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RenameUser", runtime.WithHTTPPathPattern("/api/v1/user/{old_id}/rename/{new_name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_RenameUser_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RenameUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteUser", runtime.WithHTTPPathPattern("/api/v1/user/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_DeleteUser_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListUsers", runtime.WithHTTPPathPattern("/api/v1/user")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ListUsers_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreatePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreatePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_CreatePreAuthKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreatePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpirePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpirePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ExpirePreAuthKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpirePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeletePreAuthKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeletePreAuthKey", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_DeletePreAuthKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeletePreAuthKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListPreAuthKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListPreAuthKeys", runtime.WithHTTPPathPattern("/api/v1/preauthkey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ListPreAuthKeys_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListPreAuthKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_DebugCreateNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DebugCreateNode", runtime.WithHTTPPathPattern("/api/v1/debug/node")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_DebugCreateNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DebugCreateNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_GetNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_GetNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_GetNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_SetTags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetTags", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/tags")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_SetTags_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetTags_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_SetApprovedRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetApprovedRoutes", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/approve_routes")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_SetApprovedRoutes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetApprovedRoutes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RegisterNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RegisterNode", runtime.WithHTTPPathPattern("/api/v1/node/register")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_RegisterNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RegisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_DeleteNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpireNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ExpireNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpireNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_RenameNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/RenameNode", runtime.WithHTTPPathPattern("/api/v1/node/{node_id}/rename/{new_name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_RenameNode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_RenameNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListNodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListNodes", runtime.WithHTTPPathPattern("/api/v1/node")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ListNodes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListNodes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_BackfillNodeIPs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/BackfillNodeIPs", runtime.WithHTTPPathPattern("/api/v1/node/backfillips")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_BackfillNodeIPs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_BackfillNodeIPs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthRegister_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthRegister", runtime.WithHTTPPathPattern("/api/v1/auth/register")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_AuthRegister_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthRegister_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthApprove_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthApprove", runtime.WithHTTPPathPattern("/api/v1/auth/approve")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_AuthApprove_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthApprove_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_AuthReject_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/AuthReject", runtime.WithHTTPPathPattern("/api/v1/auth/reject")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_AuthReject_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_AuthReject_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CreateApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_CreateApiKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CreateApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_ExpireApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ExpireApiKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ExpireApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_ListApiKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_ListApiKeys_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_ListApiKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_HeadscaleService_DeleteApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/DeleteApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/{prefix}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_DeleteApiKey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_DeleteApiKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_GetPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_GetPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_GetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_HeadscaleService_SetPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_SetPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_SetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_HeadscaleService_CheckPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CheckPolicy", runtime.WithHTTPPathPattern("/api/v1/policy/check")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_CheckPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_CheckPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_HeadscaleService_Health_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/Health", runtime.WithHTTPPathPattern("/api/v1/health")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_Health_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_HeadscaleService_Health_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_HeadscaleService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "user"}, "")) + pattern_HeadscaleService_RenameUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "v1", "user", "old_id", "rename", "new_name"}, "")) + pattern_HeadscaleService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "user", "id"}, "")) + pattern_HeadscaleService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "user"}, "")) + pattern_HeadscaleService_CreatePreAuthKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "preauthkey"}, "")) + pattern_HeadscaleService_ExpirePreAuthKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "preauthkey", "expire"}, "")) + pattern_HeadscaleService_DeletePreAuthKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "preauthkey"}, "")) + pattern_HeadscaleService_ListPreAuthKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "preauthkey"}, "")) + pattern_HeadscaleService_DebugCreateNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "debug", "node"}, "")) + pattern_HeadscaleService_GetNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "node", "node_id"}, "")) + pattern_HeadscaleService_SetTags_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "node", "node_id", "tags"}, "")) + pattern_HeadscaleService_SetApprovedRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "node", "node_id", "approve_routes"}, "")) + pattern_HeadscaleService_RegisterNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "node", "register"}, "")) + pattern_HeadscaleService_DeleteNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "node", "node_id"}, "")) + pattern_HeadscaleService_ExpireNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "node", "node_id", "expire"}, "")) + pattern_HeadscaleService_RenameNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "v1", "node", "node_id", "rename", "new_name"}, "")) + pattern_HeadscaleService_ListNodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "node"}, "")) + pattern_HeadscaleService_BackfillNodeIPs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "node", "backfillips"}, "")) + pattern_HeadscaleService_AuthRegister_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "register"}, "")) + pattern_HeadscaleService_AuthApprove_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "approve"}, "")) + pattern_HeadscaleService_AuthReject_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "auth", "reject"}, "")) + pattern_HeadscaleService_CreateApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, "")) + pattern_HeadscaleService_ExpireApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "apikey", "expire"}, "")) + pattern_HeadscaleService_ListApiKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, "")) + pattern_HeadscaleService_DeleteApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "apikey", "prefix"}, "")) + pattern_HeadscaleService_GetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, "")) + pattern_HeadscaleService_SetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, "")) + pattern_HeadscaleService_CheckPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "policy", "check"}, "")) + pattern_HeadscaleService_Health_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "health"}, "")) +) + +var ( + forward_HeadscaleService_CreateUser_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_RenameUser_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_DeleteUser_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ListUsers_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_CreatePreAuthKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ExpirePreAuthKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_DeletePreAuthKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ListPreAuthKeys_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_DebugCreateNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_GetNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_SetTags_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_SetApprovedRoutes_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_RegisterNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_DeleteNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ExpireNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_RenameNode_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ListNodes_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_BackfillNodeIPs_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_AuthRegister_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_AuthApprove_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_AuthReject_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_CreateApiKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ExpireApiKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_ListApiKeys_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_DeleteApiKey_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_GetPolicy_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_SetPolicy_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_CheckPolicy_0 = runtime.ForwardResponseMessage + forward_HeadscaleService_Health_0 = runtime.ForwardResponseMessage +) diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go new file mode 100644 index 000000000..9c1e38e77 --- /dev/null +++ b/gen/go/headscale/v1/headscale_grpc.pb.go @@ -0,0 +1,1199 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc (unknown) +// source: headscale/v1/headscale.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + HeadscaleService_CreateUser_FullMethodName = "/headscale.v1.HeadscaleService/CreateUser" + HeadscaleService_RenameUser_FullMethodName = "/headscale.v1.HeadscaleService/RenameUser" + HeadscaleService_DeleteUser_FullMethodName = "/headscale.v1.HeadscaleService/DeleteUser" + HeadscaleService_ListUsers_FullMethodName = "/headscale.v1.HeadscaleService/ListUsers" + HeadscaleService_CreatePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/CreatePreAuthKey" + HeadscaleService_ExpirePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpirePreAuthKey" + HeadscaleService_DeletePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/DeletePreAuthKey" + HeadscaleService_ListPreAuthKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListPreAuthKeys" + HeadscaleService_DebugCreateNode_FullMethodName = "/headscale.v1.HeadscaleService/DebugCreateNode" + HeadscaleService_GetNode_FullMethodName = "/headscale.v1.HeadscaleService/GetNode" + HeadscaleService_SetTags_FullMethodName = "/headscale.v1.HeadscaleService/SetTags" + HeadscaleService_SetApprovedRoutes_FullMethodName = "/headscale.v1.HeadscaleService/SetApprovedRoutes" + HeadscaleService_RegisterNode_FullMethodName = "/headscale.v1.HeadscaleService/RegisterNode" + HeadscaleService_DeleteNode_FullMethodName = "/headscale.v1.HeadscaleService/DeleteNode" + HeadscaleService_ExpireNode_FullMethodName = "/headscale.v1.HeadscaleService/ExpireNode" + HeadscaleService_RenameNode_FullMethodName = "/headscale.v1.HeadscaleService/RenameNode" + HeadscaleService_ListNodes_FullMethodName = "/headscale.v1.HeadscaleService/ListNodes" + HeadscaleService_BackfillNodeIPs_FullMethodName = "/headscale.v1.HeadscaleService/BackfillNodeIPs" + HeadscaleService_AuthRegister_FullMethodName = "/headscale.v1.HeadscaleService/AuthRegister" + HeadscaleService_AuthApprove_FullMethodName = "/headscale.v1.HeadscaleService/AuthApprove" + HeadscaleService_AuthReject_FullMethodName = "/headscale.v1.HeadscaleService/AuthReject" + HeadscaleService_CreateApiKey_FullMethodName = "/headscale.v1.HeadscaleService/CreateApiKey" + HeadscaleService_ExpireApiKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpireApiKey" + HeadscaleService_ListApiKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListApiKeys" + HeadscaleService_DeleteApiKey_FullMethodName = "/headscale.v1.HeadscaleService/DeleteApiKey" + HeadscaleService_GetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/GetPolicy" + HeadscaleService_SetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/SetPolicy" + HeadscaleService_CheckPolicy_FullMethodName = "/headscale.v1.HeadscaleService/CheckPolicy" + HeadscaleService_Health_FullMethodName = "/headscale.v1.HeadscaleService/Health" +) + +// HeadscaleServiceClient is the client API for HeadscaleService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HeadscaleServiceClient interface { + // --- User start --- + CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) + RenameUser(ctx context.Context, in *RenameUserRequest, opts ...grpc.CallOption) (*RenameUserResponse, error) + DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, error) + ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) + // --- PreAuthKeys start --- + CreatePreAuthKey(ctx context.Context, in *CreatePreAuthKeyRequest, opts ...grpc.CallOption) (*CreatePreAuthKeyResponse, error) + ExpirePreAuthKey(ctx context.Context, in *ExpirePreAuthKeyRequest, opts ...grpc.CallOption) (*ExpirePreAuthKeyResponse, error) + DeletePreAuthKey(ctx context.Context, in *DeletePreAuthKeyRequest, opts ...grpc.CallOption) (*DeletePreAuthKeyResponse, error) + ListPreAuthKeys(ctx context.Context, in *ListPreAuthKeysRequest, opts ...grpc.CallOption) (*ListPreAuthKeysResponse, error) + // --- Node start --- + DebugCreateNode(ctx context.Context, in *DebugCreateNodeRequest, opts ...grpc.CallOption) (*DebugCreateNodeResponse, error) + GetNode(ctx context.Context, in *GetNodeRequest, opts ...grpc.CallOption) (*GetNodeResponse, error) + SetTags(ctx context.Context, in *SetTagsRequest, opts ...grpc.CallOption) (*SetTagsResponse, error) + SetApprovedRoutes(ctx context.Context, in *SetApprovedRoutesRequest, opts ...grpc.CallOption) (*SetApprovedRoutesResponse, error) + RegisterNode(ctx context.Context, in *RegisterNodeRequest, opts ...grpc.CallOption) (*RegisterNodeResponse, error) + DeleteNode(ctx context.Context, in *DeleteNodeRequest, opts ...grpc.CallOption) (*DeleteNodeResponse, error) + ExpireNode(ctx context.Context, in *ExpireNodeRequest, opts ...grpc.CallOption) (*ExpireNodeResponse, error) + RenameNode(ctx context.Context, in *RenameNodeRequest, opts ...grpc.CallOption) (*RenameNodeResponse, error) + ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error) + BackfillNodeIPs(ctx context.Context, in *BackfillNodeIPsRequest, opts ...grpc.CallOption) (*BackfillNodeIPsResponse, error) + // --- Auth start --- + AuthRegister(ctx context.Context, in *AuthRegisterRequest, opts ...grpc.CallOption) (*AuthRegisterResponse, error) + AuthApprove(ctx context.Context, in *AuthApproveRequest, opts ...grpc.CallOption) (*AuthApproveResponse, error) + AuthReject(ctx context.Context, in *AuthRejectRequest, opts ...grpc.CallOption) (*AuthRejectResponse, error) + // --- ApiKeys start --- + CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) + ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) + ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) + DeleteApiKey(ctx context.Context, in *DeleteApiKeyRequest, opts ...grpc.CallOption) (*DeleteApiKeyResponse, error) + // --- Policy start --- + GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) + SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error) + CheckPolicy(ctx context.Context, in *CheckPolicyRequest, opts ...grpc.CallOption) (*CheckPolicyResponse, error) + // --- Health start --- + Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) +} + +type headscaleServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClient { + return &headscaleServiceClient{cc} +} + +func (c *headscaleServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateUserResponse) + err := c.cc.Invoke(ctx, HeadscaleService_CreateUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) RenameUser(ctx context.Context, in *RenameUserRequest, opts ...grpc.CallOption) (*RenameUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RenameUserResponse) + err := c.cc.Invoke(ctx, HeadscaleService_RenameUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteUserResponse) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListUsersResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ListUsers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) CreatePreAuthKey(ctx context.Context, in *CreatePreAuthKeyRequest, opts ...grpc.CallOption) (*CreatePreAuthKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreatePreAuthKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_CreatePreAuthKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ExpirePreAuthKey(ctx context.Context, in *ExpirePreAuthKeyRequest, opts ...grpc.CallOption) (*ExpirePreAuthKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExpirePreAuthKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ExpirePreAuthKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) DeletePreAuthKey(ctx context.Context, in *DeletePreAuthKeyRequest, opts ...grpc.CallOption) (*DeletePreAuthKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeletePreAuthKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_DeletePreAuthKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ListPreAuthKeys(ctx context.Context, in *ListPreAuthKeysRequest, opts ...grpc.CallOption) (*ListPreAuthKeysResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPreAuthKeysResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ListPreAuthKeys_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) DebugCreateNode(ctx context.Context, in *DebugCreateNodeRequest, opts ...grpc.CallOption) (*DebugCreateNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DebugCreateNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_DebugCreateNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) GetNode(ctx context.Context, in *GetNodeRequest, opts ...grpc.CallOption) (*GetNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_GetNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) SetTags(ctx context.Context, in *SetTagsRequest, opts ...grpc.CallOption) (*SetTagsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetTagsResponse) + err := c.cc.Invoke(ctx, HeadscaleService_SetTags_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) SetApprovedRoutes(ctx context.Context, in *SetApprovedRoutesRequest, opts ...grpc.CallOption) (*SetApprovedRoutesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetApprovedRoutesResponse) + err := c.cc.Invoke(ctx, HeadscaleService_SetApprovedRoutes_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) RegisterNode(ctx context.Context, in *RegisterNodeRequest, opts ...grpc.CallOption) (*RegisterNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RegisterNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_RegisterNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) DeleteNode(ctx context.Context, in *DeleteNodeRequest, opts ...grpc.CallOption) (*DeleteNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ExpireNode(ctx context.Context, in *ExpireNodeRequest, opts ...grpc.CallOption) (*ExpireNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExpireNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ExpireNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) RenameNode(ctx context.Context, in *RenameNodeRequest, opts ...grpc.CallOption) (*RenameNodeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RenameNodeResponse) + err := c.cc.Invoke(ctx, HeadscaleService_RenameNode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListNodesResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ListNodes_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) BackfillNodeIPs(ctx context.Context, in *BackfillNodeIPsRequest, opts ...grpc.CallOption) (*BackfillNodeIPsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BackfillNodeIPsResponse) + err := c.cc.Invoke(ctx, HeadscaleService_BackfillNodeIPs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) AuthRegister(ctx context.Context, in *AuthRegisterRequest, opts ...grpc.CallOption) (*AuthRegisterResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthRegisterResponse) + err := c.cc.Invoke(ctx, HeadscaleService_AuthRegister_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) AuthApprove(ctx context.Context, in *AuthApproveRequest, opts ...grpc.CallOption) (*AuthApproveResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthApproveResponse) + err := c.cc.Invoke(ctx, HeadscaleService_AuthApprove_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) AuthReject(ctx context.Context, in *AuthRejectRequest, opts ...grpc.CallOption) (*AuthRejectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthRejectResponse) + err := c.cc.Invoke(ctx, HeadscaleService_AuthReject_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateApiKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_CreateApiKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExpireApiKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ExpireApiKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListApiKeysResponse) + err := c.cc.Invoke(ctx, HeadscaleService_ListApiKeys_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) DeleteApiKey(ctx context.Context, in *DeleteApiKeyRequest, opts ...grpc.CallOption) (*DeleteApiKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteApiKeyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteApiKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetPolicyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_GetPolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetPolicyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_SetPolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) CheckPolicy(ctx context.Context, in *CheckPolicyRequest, opts ...grpc.CallOption) (*CheckPolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CheckPolicyResponse) + err := c.cc.Invoke(ctx, HeadscaleService_CheckPolicy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *headscaleServiceClient) Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthResponse) + err := c.cc.Invoke(ctx, HeadscaleService_Health_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HeadscaleServiceServer is the server API for HeadscaleService service. +// All implementations must embed UnimplementedHeadscaleServiceServer +// for forward compatibility. +type HeadscaleServiceServer interface { + // --- User start --- + CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) + RenameUser(context.Context, *RenameUserRequest) (*RenameUserResponse, error) + DeleteUser(context.Context, *DeleteUserRequest) (*DeleteUserResponse, error) + ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) + // --- PreAuthKeys start --- + CreatePreAuthKey(context.Context, *CreatePreAuthKeyRequest) (*CreatePreAuthKeyResponse, error) + ExpirePreAuthKey(context.Context, *ExpirePreAuthKeyRequest) (*ExpirePreAuthKeyResponse, error) + DeletePreAuthKey(context.Context, *DeletePreAuthKeyRequest) (*DeletePreAuthKeyResponse, error) + ListPreAuthKeys(context.Context, *ListPreAuthKeysRequest) (*ListPreAuthKeysResponse, error) + // --- Node start --- + DebugCreateNode(context.Context, *DebugCreateNodeRequest) (*DebugCreateNodeResponse, error) + GetNode(context.Context, *GetNodeRequest) (*GetNodeResponse, error) + SetTags(context.Context, *SetTagsRequest) (*SetTagsResponse, error) + SetApprovedRoutes(context.Context, *SetApprovedRoutesRequest) (*SetApprovedRoutesResponse, error) + RegisterNode(context.Context, *RegisterNodeRequest) (*RegisterNodeResponse, error) + DeleteNode(context.Context, *DeleteNodeRequest) (*DeleteNodeResponse, error) + ExpireNode(context.Context, *ExpireNodeRequest) (*ExpireNodeResponse, error) + RenameNode(context.Context, *RenameNodeRequest) (*RenameNodeResponse, error) + ListNodes(context.Context, *ListNodesRequest) (*ListNodesResponse, error) + BackfillNodeIPs(context.Context, *BackfillNodeIPsRequest) (*BackfillNodeIPsResponse, error) + // --- Auth start --- + AuthRegister(context.Context, *AuthRegisterRequest) (*AuthRegisterResponse, error) + AuthApprove(context.Context, *AuthApproveRequest) (*AuthApproveResponse, error) + AuthReject(context.Context, *AuthRejectRequest) (*AuthRejectResponse, error) + // --- ApiKeys start --- + CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error) + ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error) + ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error) + DeleteApiKey(context.Context, *DeleteApiKeyRequest) (*DeleteApiKeyResponse, error) + // --- Policy start --- + GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error) + SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error) + CheckPolicy(context.Context, *CheckPolicyRequest) (*CheckPolicyResponse, error) + // --- Health start --- + Health(context.Context, *HealthRequest) (*HealthResponse, error) + mustEmbedUnimplementedHeadscaleServiceServer() +} + +// UnimplementedHeadscaleServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedHeadscaleServiceServer struct{} + +func (UnimplementedHeadscaleServiceServer) CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateUser not implemented") +} +func (UnimplementedHeadscaleServiceServer) RenameUser(context.Context, *RenameUserRequest) (*RenameUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RenameUser not implemented") +} +func (UnimplementedHeadscaleServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*DeleteUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteUser not implemented") +} +func (UnimplementedHeadscaleServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListUsers not implemented") +} +func (UnimplementedHeadscaleServiceServer) CreatePreAuthKey(context.Context, *CreatePreAuthKeyRequest) (*CreatePreAuthKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreatePreAuthKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ExpirePreAuthKey(context.Context, *ExpirePreAuthKeyRequest) (*ExpirePreAuthKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExpirePreAuthKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) DeletePreAuthKey(context.Context, *DeletePreAuthKeyRequest) (*DeletePreAuthKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeletePreAuthKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ListPreAuthKeys(context.Context, *ListPreAuthKeysRequest) (*ListPreAuthKeysResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPreAuthKeys not implemented") +} +func (UnimplementedHeadscaleServiceServer) DebugCreateNode(context.Context, *DebugCreateNodeRequest) (*DebugCreateNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DebugCreateNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) GetNode(context.Context, *GetNodeRequest) (*GetNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) SetTags(context.Context, *SetTagsRequest) (*SetTagsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetTags not implemented") +} +func (UnimplementedHeadscaleServiceServer) SetApprovedRoutes(context.Context, *SetApprovedRoutesRequest) (*SetApprovedRoutesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetApprovedRoutes not implemented") +} +func (UnimplementedHeadscaleServiceServer) RegisterNode(context.Context, *RegisterNodeRequest) (*RegisterNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RegisterNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) DeleteNode(context.Context, *DeleteNodeRequest) (*DeleteNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) ExpireNode(context.Context, *ExpireNodeRequest) (*ExpireNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExpireNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) RenameNode(context.Context, *RenameNodeRequest) (*RenameNodeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RenameNode not implemented") +} +func (UnimplementedHeadscaleServiceServer) ListNodes(context.Context, *ListNodesRequest) (*ListNodesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListNodes not implemented") +} +func (UnimplementedHeadscaleServiceServer) BackfillNodeIPs(context.Context, *BackfillNodeIPsRequest) (*BackfillNodeIPsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BackfillNodeIPs not implemented") +} +func (UnimplementedHeadscaleServiceServer) AuthRegister(context.Context, *AuthRegisterRequest) (*AuthRegisterResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthRegister not implemented") +} +func (UnimplementedHeadscaleServiceServer) AuthApprove(context.Context, *AuthApproveRequest) (*AuthApproveResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthApprove not implemented") +} +func (UnimplementedHeadscaleServiceServer) AuthReject(context.Context, *AuthRejectRequest) (*AuthRejectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AuthReject not implemented") +} +func (UnimplementedHeadscaleServiceServer) CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateApiKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExpireApiKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListApiKeys not implemented") +} +func (UnimplementedHeadscaleServiceServer) DeleteApiKey(context.Context, *DeleteApiKeyRequest) (*DeleteApiKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteApiKey not implemented") +} +func (UnimplementedHeadscaleServiceServer) GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetPolicy not implemented") +} +func (UnimplementedHeadscaleServiceServer) SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetPolicy not implemented") +} +func (UnimplementedHeadscaleServiceServer) CheckPolicy(context.Context, *CheckPolicyRequest) (*CheckPolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CheckPolicy not implemented") +} +func (UnimplementedHeadscaleServiceServer) Health(context.Context, *HealthRequest) (*HealthResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Health not implemented") +} +func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {} +func (UnimplementedHeadscaleServiceServer) testEmbeddedByValue() {} + +// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HeadscaleServiceServer will +// result in compilation errors. +type UnsafeHeadscaleServiceServer interface { + mustEmbedUnimplementedHeadscaleServiceServer() +} + +func RegisterHeadscaleServiceServer(s grpc.ServiceRegistrar, srv HeadscaleServiceServer) { + // If the following call panics, it indicates UnimplementedHeadscaleServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&HeadscaleService_ServiceDesc, srv) +} + +func _HeadscaleService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).CreateUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_CreateUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).CreateUser(ctx, req.(*CreateUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_RenameUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RenameUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).RenameUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_RenameUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).RenameUser(ctx, req.(*RenameUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).DeleteUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_DeleteUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).DeleteUser(ctx, req.(*DeleteUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ListUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListUsersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ListUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ListUsers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ListUsers(ctx, req.(*ListUsersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_CreatePreAuthKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePreAuthKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_CreatePreAuthKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, req.(*CreatePreAuthKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ExpirePreAuthKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExpirePreAuthKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ExpirePreAuthKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, req.(*ExpirePreAuthKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_DeletePreAuthKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePreAuthKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).DeletePreAuthKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_DeletePreAuthKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).DeletePreAuthKey(ctx, req.(*DeletePreAuthKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ListPreAuthKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPreAuthKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ListPreAuthKeys_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, req.(*ListPreAuthKeysRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_DebugCreateNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DebugCreateNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).DebugCreateNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_DebugCreateNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).DebugCreateNode(ctx, req.(*DebugCreateNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_GetNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).GetNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_GetNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).GetNode(ctx, req.(*GetNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_SetTags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetTagsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).SetTags(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_SetTags_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).SetTags(ctx, req.(*SetTagsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_SetApprovedRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetApprovedRoutesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).SetApprovedRoutes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_SetApprovedRoutes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).SetApprovedRoutes(ctx, req.(*SetApprovedRoutesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_RegisterNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).RegisterNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_RegisterNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).RegisterNode(ctx, req.(*RegisterNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_DeleteNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).DeleteNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_DeleteNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).DeleteNode(ctx, req.(*DeleteNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ExpireNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExpireNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ExpireNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ExpireNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ExpireNode(ctx, req.(*ExpireNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_RenameNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RenameNodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).RenameNode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_RenameNode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).RenameNode(ctx, req.(*RenameNodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ListNodes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListNodesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ListNodes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ListNodes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ListNodes(ctx, req.(*ListNodesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_BackfillNodeIPs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackfillNodeIPsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).BackfillNodeIPs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_BackfillNodeIPs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).BackfillNodeIPs(ctx, req.(*BackfillNodeIPsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_AuthRegister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthRegisterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).AuthRegister(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_AuthRegister_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).AuthRegister(ctx, req.(*AuthRegisterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_AuthApprove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthApproveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).AuthApprove(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_AuthApprove_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).AuthApprove(ctx, req.(*AuthApproveRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_AuthReject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthRejectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).AuthReject(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_AuthReject_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).AuthReject(ctx, req.(*AuthRejectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_CreateApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateApiKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).CreateApiKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_CreateApiKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).CreateApiKey(ctx, req.(*CreateApiKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ExpireApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExpireApiKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ExpireApiKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, req.(*ExpireApiKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_ListApiKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListApiKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).ListApiKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_ListApiKeys_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).ListApiKeys(ctx, req.(*ListApiKeysRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_DeleteApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteApiKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).DeleteApiKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_DeleteApiKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).DeleteApiKey(ctx, req.(*DeleteApiKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_GetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).GetPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_GetPolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).GetPolicy(ctx, req.(*GetPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_SetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).SetPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_SetPolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).SetPolicy(ctx, req.(*SetPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_CheckPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).CheckPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_CheckPolicy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).CheckPolicy(ctx, req.(*CheckPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HeadscaleService_Health_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).Health(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HeadscaleService_Health_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).Health(ctx, req.(*HealthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var HeadscaleService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "headscale.v1.HeadscaleService", + HandlerType: (*HeadscaleServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateUser", + Handler: _HeadscaleService_CreateUser_Handler, + }, + { + MethodName: "RenameUser", + Handler: _HeadscaleService_RenameUser_Handler, + }, + { + MethodName: "DeleteUser", + Handler: _HeadscaleService_DeleteUser_Handler, + }, + { + MethodName: "ListUsers", + Handler: _HeadscaleService_ListUsers_Handler, + }, + { + MethodName: "CreatePreAuthKey", + Handler: _HeadscaleService_CreatePreAuthKey_Handler, + }, + { + MethodName: "ExpirePreAuthKey", + Handler: _HeadscaleService_ExpirePreAuthKey_Handler, + }, + { + MethodName: "DeletePreAuthKey", + Handler: _HeadscaleService_DeletePreAuthKey_Handler, + }, + { + MethodName: "ListPreAuthKeys", + Handler: _HeadscaleService_ListPreAuthKeys_Handler, + }, + { + MethodName: "DebugCreateNode", + Handler: _HeadscaleService_DebugCreateNode_Handler, + }, + { + MethodName: "GetNode", + Handler: _HeadscaleService_GetNode_Handler, + }, + { + MethodName: "SetTags", + Handler: _HeadscaleService_SetTags_Handler, + }, + { + MethodName: "SetApprovedRoutes", + Handler: _HeadscaleService_SetApprovedRoutes_Handler, + }, + { + MethodName: "RegisterNode", + Handler: _HeadscaleService_RegisterNode_Handler, + }, + { + MethodName: "DeleteNode", + Handler: _HeadscaleService_DeleteNode_Handler, + }, + { + MethodName: "ExpireNode", + Handler: _HeadscaleService_ExpireNode_Handler, + }, + { + MethodName: "RenameNode", + Handler: _HeadscaleService_RenameNode_Handler, + }, + { + MethodName: "ListNodes", + Handler: _HeadscaleService_ListNodes_Handler, + }, + { + MethodName: "BackfillNodeIPs", + Handler: _HeadscaleService_BackfillNodeIPs_Handler, + }, + { + MethodName: "AuthRegister", + Handler: _HeadscaleService_AuthRegister_Handler, + }, + { + MethodName: "AuthApprove", + Handler: _HeadscaleService_AuthApprove_Handler, + }, + { + MethodName: "AuthReject", + Handler: _HeadscaleService_AuthReject_Handler, + }, + { + MethodName: "CreateApiKey", + Handler: _HeadscaleService_CreateApiKey_Handler, + }, + { + MethodName: "ExpireApiKey", + Handler: _HeadscaleService_ExpireApiKey_Handler, + }, + { + MethodName: "ListApiKeys", + Handler: _HeadscaleService_ListApiKeys_Handler, + }, + { + MethodName: "DeleteApiKey", + Handler: _HeadscaleService_DeleteApiKey_Handler, + }, + { + MethodName: "GetPolicy", + Handler: _HeadscaleService_GetPolicy_Handler, + }, + { + MethodName: "SetPolicy", + Handler: _HeadscaleService_SetPolicy_Handler, + }, + { + MethodName: "CheckPolicy", + Handler: _HeadscaleService_CheckPolicy_Handler, + }, + { + MethodName: "Health", + Handler: _HeadscaleService_Health_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "headscale/v1/headscale.proto", +} diff --git a/gen/go/headscale/v1/node.pb.go b/gen/go/headscale/v1/node.pb.go new file mode 100644 index 000000000..32142ed8b --- /dev/null +++ b/gen/go/headscale/v1/node.pb.go @@ -0,0 +1,1379 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/node.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RegisterMethod int32 + +const ( + RegisterMethod_REGISTER_METHOD_UNSPECIFIED RegisterMethod = 0 + RegisterMethod_REGISTER_METHOD_AUTH_KEY RegisterMethod = 1 + RegisterMethod_REGISTER_METHOD_CLI RegisterMethod = 2 + RegisterMethod_REGISTER_METHOD_OIDC RegisterMethod = 3 +) + +// Enum value maps for RegisterMethod. +var ( + RegisterMethod_name = map[int32]string{ + 0: "REGISTER_METHOD_UNSPECIFIED", + 1: "REGISTER_METHOD_AUTH_KEY", + 2: "REGISTER_METHOD_CLI", + 3: "REGISTER_METHOD_OIDC", + } + RegisterMethod_value = map[string]int32{ + "REGISTER_METHOD_UNSPECIFIED": 0, + "REGISTER_METHOD_AUTH_KEY": 1, + "REGISTER_METHOD_CLI": 2, + "REGISTER_METHOD_OIDC": 3, + } +) + +func (x RegisterMethod) Enum() *RegisterMethod { + p := new(RegisterMethod) + *p = x + return p +} + +func (x RegisterMethod) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RegisterMethod) Descriptor() protoreflect.EnumDescriptor { + return file_headscale_v1_node_proto_enumTypes[0].Descriptor() +} + +func (RegisterMethod) Type() protoreflect.EnumType { + return &file_headscale_v1_node_proto_enumTypes[0] +} + +func (x RegisterMethod) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RegisterMethod.Descriptor instead. +func (RegisterMethod) EnumDescriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{0} +} + +type Node struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + MachineKey string `protobuf:"bytes,2,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"` + NodeKey string `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + DiscoKey string `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"` + IpAddresses []string `protobuf:"bytes,5,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` + User *User `protobuf:"bytes,7,opt,name=user,proto3" json:"user,omitempty"` + LastSeen *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Expiry *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expiry,proto3" json:"expiry,omitempty"` + PreAuthKey *PreAuthKey `protobuf:"bytes,11,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + RegisterMethod RegisterMethod `protobuf:"varint,13,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"` + // Deprecated + // repeated string forced_tags = 18; + // repeated string invalid_tags = 19; + // repeated string valid_tags = 20; + GivenName string `protobuf:"bytes,21,opt,name=given_name,json=givenName,proto3" json:"given_name,omitempty"` + Online bool `protobuf:"varint,22,opt,name=online,proto3" json:"online,omitempty"` + ApprovedRoutes []string `protobuf:"bytes,23,rep,name=approved_routes,json=approvedRoutes,proto3" json:"approved_routes,omitempty"` + AvailableRoutes []string `protobuf:"bytes,24,rep,name=available_routes,json=availableRoutes,proto3" json:"available_routes,omitempty"` + SubnetRoutes []string `protobuf:"bytes,25,rep,name=subnet_routes,json=subnetRoutes,proto3" json:"subnet_routes,omitempty"` + Tags []string `protobuf:"bytes,26,rep,name=tags,proto3" json:"tags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Node) Reset() { + *x = Node{} + mi := &file_headscale_v1_node_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Node) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Node) ProtoMessage() {} + +func (x *Node) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Node.ProtoReflect.Descriptor instead. +func (*Node) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{0} +} + +func (x *Node) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Node) GetMachineKey() string { + if x != nil { + return x.MachineKey + } + return "" +} + +func (x *Node) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *Node) GetDiscoKey() string { + if x != nil { + return x.DiscoKey + } + return "" +} + +func (x *Node) GetIpAddresses() []string { + if x != nil { + return x.IpAddresses + } + return nil +} + +func (x *Node) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Node) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +func (x *Node) GetLastSeen() *timestamppb.Timestamp { + if x != nil { + return x.LastSeen + } + return nil +} + +func (x *Node) GetExpiry() *timestamppb.Timestamp { + if x != nil { + return x.Expiry + } + return nil +} + +func (x *Node) GetPreAuthKey() *PreAuthKey { + if x != nil { + return x.PreAuthKey + } + return nil +} + +func (x *Node) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Node) GetRegisterMethod() RegisterMethod { + if x != nil { + return x.RegisterMethod + } + return RegisterMethod_REGISTER_METHOD_UNSPECIFIED +} + +func (x *Node) GetGivenName() string { + if x != nil { + return x.GivenName + } + return "" +} + +func (x *Node) GetOnline() bool { + if x != nil { + return x.Online + } + return false +} + +func (x *Node) GetApprovedRoutes() []string { + if x != nil { + return x.ApprovedRoutes + } + return nil +} + +func (x *Node) GetAvailableRoutes() []string { + if x != nil { + return x.AvailableRoutes + } + return nil +} + +func (x *Node) GetSubnetRoutes() []string { + if x != nil { + return x.SubnetRoutes + } + return nil +} + +func (x *Node) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + +type RegisterNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterNodeRequest) Reset() { + *x = RegisterNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterNodeRequest) ProtoMessage() {} + +func (x *RegisterNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterNodeRequest.ProtoReflect.Descriptor instead. +func (*RegisterNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterNodeRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *RegisterNodeRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type RegisterNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterNodeResponse) Reset() { + *x = RegisterNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterNodeResponse) ProtoMessage() {} + +func (x *RegisterNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterNodeResponse.ProtoReflect.Descriptor instead. +func (*RegisterNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{2} +} + +func (x *RegisterNodeResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type GetNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetNodeRequest) Reset() { + *x = GetNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetNodeRequest) ProtoMessage() {} + +func (x *GetNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetNodeRequest.ProtoReflect.Descriptor instead. +func (*GetNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{3} +} + +func (x *GetNodeRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +type GetNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetNodeResponse) Reset() { + *x = GetNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetNodeResponse) ProtoMessage() {} + +func (x *GetNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetNodeResponse.ProtoReflect.Descriptor instead. +func (*GetNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{4} +} + +func (x *GetNodeResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type SetTagsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + Tags []string `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetTagsRequest) Reset() { + *x = SetTagsRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetTagsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTagsRequest) ProtoMessage() {} + +func (x *SetTagsRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTagsRequest.ProtoReflect.Descriptor instead. +func (*SetTagsRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{5} +} + +func (x *SetTagsRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +func (x *SetTagsRequest) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + +type SetTagsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetTagsResponse) Reset() { + *x = SetTagsResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetTagsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTagsResponse) ProtoMessage() {} + +func (x *SetTagsResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetTagsResponse.ProtoReflect.Descriptor instead. +func (*SetTagsResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{6} +} + +func (x *SetTagsResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type SetApprovedRoutesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + Routes []string `protobuf:"bytes,2,rep,name=routes,proto3" json:"routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetApprovedRoutesRequest) Reset() { + *x = SetApprovedRoutesRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetApprovedRoutesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetApprovedRoutesRequest) ProtoMessage() {} + +func (x *SetApprovedRoutesRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetApprovedRoutesRequest.ProtoReflect.Descriptor instead. +func (*SetApprovedRoutesRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{7} +} + +func (x *SetApprovedRoutesRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +func (x *SetApprovedRoutesRequest) GetRoutes() []string { + if x != nil { + return x.Routes + } + return nil +} + +type SetApprovedRoutesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetApprovedRoutesResponse) Reset() { + *x = SetApprovedRoutesResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetApprovedRoutesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetApprovedRoutesResponse) ProtoMessage() {} + +func (x *SetApprovedRoutesResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetApprovedRoutesResponse.ProtoReflect.Descriptor instead. +func (*SetApprovedRoutesResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{8} +} + +func (x *SetApprovedRoutesResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type DeleteNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNodeRequest) Reset() { + *x = DeleteNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNodeRequest) ProtoMessage() {} + +func (x *DeleteNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNodeRequest.ProtoReflect.Descriptor instead. +func (*DeleteNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{9} +} + +func (x *DeleteNodeRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +type DeleteNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteNodeResponse) Reset() { + *x = DeleteNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteNodeResponse) ProtoMessage() {} + +func (x *DeleteNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteNodeResponse.ProtoReflect.Descriptor instead. +func (*DeleteNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{10} +} + +type ExpireNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + Expiry *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiry,proto3" json:"expiry,omitempty"` + // When true, sets expiry to null (node will never expire). + DisableExpiry bool `protobuf:"varint,3,opt,name=disable_expiry,json=disableExpiry,proto3" json:"disable_expiry,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpireNodeRequest) Reset() { + *x = ExpireNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpireNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireNodeRequest) ProtoMessage() {} + +func (x *ExpireNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpireNodeRequest.ProtoReflect.Descriptor instead. +func (*ExpireNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{11} +} + +func (x *ExpireNodeRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +func (x *ExpireNodeRequest) GetExpiry() *timestamppb.Timestamp { + if x != nil { + return x.Expiry + } + return nil +} + +func (x *ExpireNodeRequest) GetDisableExpiry() bool { + if x != nil { + return x.DisableExpiry + } + return false +} + +type ExpireNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpireNodeResponse) Reset() { + *x = ExpireNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpireNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireNodeResponse) ProtoMessage() {} + +func (x *ExpireNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpireNodeResponse.ProtoReflect.Descriptor instead. +func (*ExpireNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{12} +} + +func (x *ExpireNodeResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type RenameNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NodeId uint64 `protobuf:"varint,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + NewName string `protobuf:"bytes,2,opt,name=new_name,json=newName,proto3" json:"new_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RenameNodeRequest) Reset() { + *x = RenameNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RenameNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RenameNodeRequest) ProtoMessage() {} + +func (x *RenameNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RenameNodeRequest.ProtoReflect.Descriptor instead. +func (*RenameNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{13} +} + +func (x *RenameNodeRequest) GetNodeId() uint64 { + if x != nil { + return x.NodeId + } + return 0 +} + +func (x *RenameNodeRequest) GetNewName() string { + if x != nil { + return x.NewName + } + return "" +} + +type RenameNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RenameNodeResponse) Reset() { + *x = RenameNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RenameNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RenameNodeResponse) ProtoMessage() {} + +func (x *RenameNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RenameNodeResponse.ProtoReflect.Descriptor instead. +func (*RenameNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{14} +} + +func (x *RenameNodeResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type ListNodesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNodesRequest) Reset() { + *x = ListNodesRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNodesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNodesRequest) ProtoMessage() {} + +func (x *ListNodesRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNodesRequest.ProtoReflect.Descriptor instead. +func (*ListNodesRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{15} +} + +func (x *ListNodesRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +type ListNodesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Nodes []*Node `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNodesResponse) Reset() { + *x = ListNodesResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNodesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNodesResponse) ProtoMessage() {} + +func (x *ListNodesResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNodesResponse.ProtoReflect.Descriptor instead. +func (*ListNodesResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{16} +} + +func (x *ListNodesResponse) GetNodes() []*Node { + if x != nil { + return x.Nodes + } + return nil +} + +type DebugCreateNodeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Routes []string `protobuf:"bytes,4,rep,name=routes,proto3" json:"routes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugCreateNodeRequest) Reset() { + *x = DebugCreateNodeRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugCreateNodeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugCreateNodeRequest) ProtoMessage() {} + +func (x *DebugCreateNodeRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugCreateNodeRequest.ProtoReflect.Descriptor instead. +func (*DebugCreateNodeRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{17} +} + +func (x *DebugCreateNodeRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *DebugCreateNodeRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *DebugCreateNodeRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DebugCreateNodeRequest) GetRoutes() []string { + if x != nil { + return x.Routes + } + return nil +} + +type DebugCreateNodeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugCreateNodeResponse) Reset() { + *x = DebugCreateNodeResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugCreateNodeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugCreateNodeResponse) ProtoMessage() {} + +func (x *DebugCreateNodeResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugCreateNodeResponse.ProtoReflect.Descriptor instead. +func (*DebugCreateNodeResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{18} +} + +func (x *DebugCreateNodeResponse) GetNode() *Node { + if x != nil { + return x.Node + } + return nil +} + +type BackfillNodeIPsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Confirmed bool `protobuf:"varint,1,opt,name=confirmed,proto3" json:"confirmed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BackfillNodeIPsRequest) Reset() { + *x = BackfillNodeIPsRequest{} + mi := &file_headscale_v1_node_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BackfillNodeIPsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackfillNodeIPsRequest) ProtoMessage() {} + +func (x *BackfillNodeIPsRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackfillNodeIPsRequest.ProtoReflect.Descriptor instead. +func (*BackfillNodeIPsRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{19} +} + +func (x *BackfillNodeIPsRequest) GetConfirmed() bool { + if x != nil { + return x.Confirmed + } + return false +} + +type BackfillNodeIPsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changes []string `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BackfillNodeIPsResponse) Reset() { + *x = BackfillNodeIPsResponse{} + mi := &file_headscale_v1_node_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BackfillNodeIPsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackfillNodeIPsResponse) ProtoMessage() {} + +func (x *BackfillNodeIPsResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_node_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackfillNodeIPsResponse.ProtoReflect.Descriptor instead. +func (*BackfillNodeIPsResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_node_proto_rawDescGZIP(), []int{20} +} + +func (x *BackfillNodeIPsResponse) GetChanges() []string { + if x != nil { + return x.Changes + } + return nil +} + +var File_headscale_v1_node_proto protoreflect.FileDescriptor + +const file_headscale_v1_node_proto_rawDesc = "" + + "\n" + + "\x17headscale/v1/node.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1dheadscale/v1/preauthkey.proto\x1a\x17headscale/v1/user.proto\"\xc9\x05\n" + + "\x04Node\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x1f\n" + + "\vmachine_key\x18\x02 \x01(\tR\n" + + "machineKey\x12\x19\n" + + "\bnode_key\x18\x03 \x01(\tR\anodeKey\x12\x1b\n" + + "\tdisco_key\x18\x04 \x01(\tR\bdiscoKey\x12!\n" + + "\fip_addresses\x18\x05 \x03(\tR\vipAddresses\x12\x12\n" + + "\x04name\x18\x06 \x01(\tR\x04name\x12&\n" + + "\x04user\x18\a \x01(\v2\x12.headscale.v1.UserR\x04user\x127\n" + + "\tlast_seen\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\blastSeen\x122\n" + + "\x06expiry\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\x06expiry\x12:\n" + + "\fpre_auth_key\x18\v \x01(\v2\x18.headscale.v1.PreAuthKeyR\n" + + "preAuthKey\x129\n" + + "\n" + + "created_at\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12E\n" + + "\x0fregister_method\x18\r \x01(\x0e2\x1c.headscale.v1.RegisterMethodR\x0eregisterMethod\x12\x1d\n" + + "\n" + + "given_name\x18\x15 \x01(\tR\tgivenName\x12\x16\n" + + "\x06online\x18\x16 \x01(\bR\x06online\x12'\n" + + "\x0fapproved_routes\x18\x17 \x03(\tR\x0eapprovedRoutes\x12)\n" + + "\x10available_routes\x18\x18 \x03(\tR\x0favailableRoutes\x12#\n" + + "\rsubnet_routes\x18\x19 \x03(\tR\fsubnetRoutes\x12\x12\n" + + "\x04tags\x18\x1a \x03(\tR\x04tagsJ\x04\b\t\x10\n" + + "J\x04\b\x0e\x10\x15\";\n" + + "\x13RegisterNodeRequest\x12\x12\n" + + "\x04user\x18\x01 \x01(\tR\x04user\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\">\n" + + "\x14RegisterNodeResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\")\n" + + "\x0eGetNodeRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\"9\n" + + "\x0fGetNodeResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"=\n" + + "\x0eSetTagsRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\x12\x12\n" + + "\x04tags\x18\x02 \x03(\tR\x04tags\"9\n" + + "\x0fSetTagsResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"K\n" + + "\x18SetApprovedRoutesRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\x12\x16\n" + + "\x06routes\x18\x02 \x03(\tR\x06routes\"C\n" + + "\x19SetApprovedRoutesResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\",\n" + + "\x11DeleteNodeRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\"\x14\n" + + "\x12DeleteNodeResponse\"\x87\x01\n" + + "\x11ExpireNodeRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\x122\n" + + "\x06expiry\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x06expiry\x12%\n" + + "\x0edisable_expiry\x18\x03 \x01(\bR\rdisableExpiry\"<\n" + + "\x12ExpireNodeResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"G\n" + + "\x11RenameNodeRequest\x12\x17\n" + + "\anode_id\x18\x01 \x01(\x04R\x06nodeId\x12\x19\n" + + "\bnew_name\x18\x02 \x01(\tR\anewName\"<\n" + + "\x12RenameNodeResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"&\n" + + "\x10ListNodesRequest\x12\x12\n" + + "\x04user\x18\x01 \x01(\tR\x04user\"=\n" + + "\x11ListNodesResponse\x12(\n" + + "\x05nodes\x18\x01 \x03(\v2\x12.headscale.v1.NodeR\x05nodes\"j\n" + + "\x16DebugCreateNodeRequest\x12\x12\n" + + "\x04user\x18\x01 \x01(\tR\x04user\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06routes\x18\x04 \x03(\tR\x06routes\"A\n" + + "\x17DebugCreateNodeResponse\x12&\n" + + "\x04node\x18\x01 \x01(\v2\x12.headscale.v1.NodeR\x04node\"6\n" + + "\x16BackfillNodeIPsRequest\x12\x1c\n" + + "\tconfirmed\x18\x01 \x01(\bR\tconfirmed\"3\n" + + "\x17BackfillNodeIPsResponse\x12\x18\n" + + "\achanges\x18\x01 \x03(\tR\achanges*\x82\x01\n" + + "\x0eRegisterMethod\x12\x1f\n" + + "\x1bREGISTER_METHOD_UNSPECIFIED\x10\x00\x12\x1c\n" + + "\x18REGISTER_METHOD_AUTH_KEY\x10\x01\x12\x17\n" + + "\x13REGISTER_METHOD_CLI\x10\x02\x12\x18\n" + + "\x14REGISTER_METHOD_OIDC\x10\x03B)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_node_proto_rawDescOnce sync.Once + file_headscale_v1_node_proto_rawDescData []byte +) + +func file_headscale_v1_node_proto_rawDescGZIP() []byte { + file_headscale_v1_node_proto_rawDescOnce.Do(func() { + file_headscale_v1_node_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_node_proto_rawDesc), len(file_headscale_v1_node_proto_rawDesc))) + }) + return file_headscale_v1_node_proto_rawDescData +} + +var file_headscale_v1_node_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_headscale_v1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_headscale_v1_node_proto_goTypes = []any{ + (RegisterMethod)(0), // 0: headscale.v1.RegisterMethod + (*Node)(nil), // 1: headscale.v1.Node + (*RegisterNodeRequest)(nil), // 2: headscale.v1.RegisterNodeRequest + (*RegisterNodeResponse)(nil), // 3: headscale.v1.RegisterNodeResponse + (*GetNodeRequest)(nil), // 4: headscale.v1.GetNodeRequest + (*GetNodeResponse)(nil), // 5: headscale.v1.GetNodeResponse + (*SetTagsRequest)(nil), // 6: headscale.v1.SetTagsRequest + (*SetTagsResponse)(nil), // 7: headscale.v1.SetTagsResponse + (*SetApprovedRoutesRequest)(nil), // 8: headscale.v1.SetApprovedRoutesRequest + (*SetApprovedRoutesResponse)(nil), // 9: headscale.v1.SetApprovedRoutesResponse + (*DeleteNodeRequest)(nil), // 10: headscale.v1.DeleteNodeRequest + (*DeleteNodeResponse)(nil), // 11: headscale.v1.DeleteNodeResponse + (*ExpireNodeRequest)(nil), // 12: headscale.v1.ExpireNodeRequest + (*ExpireNodeResponse)(nil), // 13: headscale.v1.ExpireNodeResponse + (*RenameNodeRequest)(nil), // 14: headscale.v1.RenameNodeRequest + (*RenameNodeResponse)(nil), // 15: headscale.v1.RenameNodeResponse + (*ListNodesRequest)(nil), // 16: headscale.v1.ListNodesRequest + (*ListNodesResponse)(nil), // 17: headscale.v1.ListNodesResponse + (*DebugCreateNodeRequest)(nil), // 18: headscale.v1.DebugCreateNodeRequest + (*DebugCreateNodeResponse)(nil), // 19: headscale.v1.DebugCreateNodeResponse + (*BackfillNodeIPsRequest)(nil), // 20: headscale.v1.BackfillNodeIPsRequest + (*BackfillNodeIPsResponse)(nil), // 21: headscale.v1.BackfillNodeIPsResponse + (*User)(nil), // 22: headscale.v1.User + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*PreAuthKey)(nil), // 24: headscale.v1.PreAuthKey +} +var file_headscale_v1_node_proto_depIdxs = []int32{ + 22, // 0: headscale.v1.Node.user:type_name -> headscale.v1.User + 23, // 1: headscale.v1.Node.last_seen:type_name -> google.protobuf.Timestamp + 23, // 2: headscale.v1.Node.expiry:type_name -> google.protobuf.Timestamp + 24, // 3: headscale.v1.Node.pre_auth_key:type_name -> headscale.v1.PreAuthKey + 23, // 4: headscale.v1.Node.created_at:type_name -> google.protobuf.Timestamp + 0, // 5: headscale.v1.Node.register_method:type_name -> headscale.v1.RegisterMethod + 1, // 6: headscale.v1.RegisterNodeResponse.node:type_name -> headscale.v1.Node + 1, // 7: headscale.v1.GetNodeResponse.node:type_name -> headscale.v1.Node + 1, // 8: headscale.v1.SetTagsResponse.node:type_name -> headscale.v1.Node + 1, // 9: headscale.v1.SetApprovedRoutesResponse.node:type_name -> headscale.v1.Node + 23, // 10: headscale.v1.ExpireNodeRequest.expiry:type_name -> google.protobuf.Timestamp + 1, // 11: headscale.v1.ExpireNodeResponse.node:type_name -> headscale.v1.Node + 1, // 12: headscale.v1.RenameNodeResponse.node:type_name -> headscale.v1.Node + 1, // 13: headscale.v1.ListNodesResponse.nodes:type_name -> headscale.v1.Node + 1, // 14: headscale.v1.DebugCreateNodeResponse.node:type_name -> headscale.v1.Node + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_headscale_v1_node_proto_init() } +func file_headscale_v1_node_proto_init() { + if File_headscale_v1_node_proto != nil { + return + } + file_headscale_v1_preauthkey_proto_init() + file_headscale_v1_user_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_node_proto_rawDesc), len(file_headscale_v1_node_proto_rawDesc)), + NumEnums: 1, + NumMessages: 21, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_node_proto_goTypes, + DependencyIndexes: file_headscale_v1_node_proto_depIdxs, + EnumInfos: file_headscale_v1_node_proto_enumTypes, + MessageInfos: file_headscale_v1_node_proto_msgTypes, + }.Build() + File_headscale_v1_node_proto = out.File + file_headscale_v1_node_proto_goTypes = nil + file_headscale_v1_node_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/policy.pb.go b/gen/go/headscale/v1/policy.pb.go new file mode 100644 index 000000000..7b58ac121 --- /dev/null +++ b/gen/go/headscale/v1/policy.pb.go @@ -0,0 +1,363 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/policy.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SetPolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetPolicyRequest) Reset() { + *x = SetPolicyRequest{} + mi := &file_headscale_v1_policy_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetPolicyRequest) ProtoMessage() {} + +func (x *SetPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetPolicyRequest.ProtoReflect.Descriptor instead. +func (*SetPolicyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{0} +} + +func (x *SetPolicyRequest) GetPolicy() string { + if x != nil { + return x.Policy + } + return "" +} + +type SetPolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetPolicyResponse) Reset() { + *x = SetPolicyResponse{} + mi := &file_headscale_v1_policy_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetPolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetPolicyResponse) ProtoMessage() {} + +func (x *SetPolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetPolicyResponse.ProtoReflect.Descriptor instead. +func (*SetPolicyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{1} +} + +func (x *SetPolicyResponse) GetPolicy() string { + if x != nil { + return x.Policy + } + return "" +} + +func (x *SetPolicyResponse) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type GetPolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetPolicyRequest) Reset() { + *x = GetPolicyRequest{} + mi := &file_headscale_v1_policy_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPolicyRequest) ProtoMessage() {} + +func (x *GetPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPolicyRequest.ProtoReflect.Descriptor instead. +func (*GetPolicyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{2} +} + +type GetPolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetPolicyResponse) Reset() { + *x = GetPolicyResponse{} + mi := &file_headscale_v1_policy_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPolicyResponse) ProtoMessage() {} + +func (x *GetPolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPolicyResponse.ProtoReflect.Descriptor instead. +func (*GetPolicyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{3} +} + +func (x *GetPolicyResponse) GetPolicy() string { + if x != nil { + return x.Policy + } + return "" +} + +func (x *GetPolicyResponse) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type CheckPolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckPolicyRequest) Reset() { + *x = CheckPolicyRequest{} + mi := &file_headscale_v1_policy_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckPolicyRequest) ProtoMessage() {} + +func (x *CheckPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckPolicyRequest.ProtoReflect.Descriptor instead. +func (*CheckPolicyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{4} +} + +func (x *CheckPolicyRequest) GetPolicy() string { + if x != nil { + return x.Policy + } + return "" +} + +type CheckPolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckPolicyResponse) Reset() { + *x = CheckPolicyResponse{} + mi := &file_headscale_v1_policy_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckPolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckPolicyResponse) ProtoMessage() {} + +func (x *CheckPolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_policy_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckPolicyResponse.ProtoReflect.Descriptor instead. +func (*CheckPolicyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_policy_proto_rawDescGZIP(), []int{5} +} + +var File_headscale_v1_policy_proto protoreflect.FileDescriptor + +const file_headscale_v1_policy_proto_rawDesc = "" + + "\n" + + "\x19headscale/v1/policy.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"*\n" + + "\x10SetPolicyRequest\x12\x16\n" + + "\x06policy\x18\x01 \x01(\tR\x06policy\"f\n" + + "\x11SetPolicyResponse\x12\x16\n" + + "\x06policy\x18\x01 \x01(\tR\x06policy\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\x12\n" + + "\x10GetPolicyRequest\"f\n" + + "\x11GetPolicyResponse\x12\x16\n" + + "\x06policy\x18\x01 \x01(\tR\x06policy\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\",\n" + + "\x12CheckPolicyRequest\x12\x16\n" + + "\x06policy\x18\x01 \x01(\tR\x06policy\"\x15\n" + + "\x13CheckPolicyResponseB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_policy_proto_rawDescOnce sync.Once + file_headscale_v1_policy_proto_rawDescData []byte +) + +func file_headscale_v1_policy_proto_rawDescGZIP() []byte { + file_headscale_v1_policy_proto_rawDescOnce.Do(func() { + file_headscale_v1_policy_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_policy_proto_rawDesc), len(file_headscale_v1_policy_proto_rawDesc))) + }) + return file_headscale_v1_policy_proto_rawDescData +} + +var file_headscale_v1_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_headscale_v1_policy_proto_goTypes = []any{ + (*SetPolicyRequest)(nil), // 0: headscale.v1.SetPolicyRequest + (*SetPolicyResponse)(nil), // 1: headscale.v1.SetPolicyResponse + (*GetPolicyRequest)(nil), // 2: headscale.v1.GetPolicyRequest + (*GetPolicyResponse)(nil), // 3: headscale.v1.GetPolicyResponse + (*CheckPolicyRequest)(nil), // 4: headscale.v1.CheckPolicyRequest + (*CheckPolicyResponse)(nil), // 5: headscale.v1.CheckPolicyResponse + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp +} +var file_headscale_v1_policy_proto_depIdxs = []int32{ + 6, // 0: headscale.v1.SetPolicyResponse.updated_at:type_name -> google.protobuf.Timestamp + 6, // 1: headscale.v1.GetPolicyResponse.updated_at:type_name -> google.protobuf.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_headscale_v1_policy_proto_init() } +func file_headscale_v1_policy_proto_init() { + if File_headscale_v1_policy_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_policy_proto_rawDesc), len(file_headscale_v1_policy_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_policy_proto_goTypes, + DependencyIndexes: file_headscale_v1_policy_proto_depIdxs, + MessageInfos: file_headscale_v1_policy_proto_msgTypes, + }.Build() + File_headscale_v1_policy_proto = out.File + file_headscale_v1_policy_proto_goTypes = nil + file_headscale_v1_policy_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/preauthkey.pb.go b/gen/go/headscale/v1/preauthkey.pb.go new file mode 100644 index 000000000..ff902d45c --- /dev/null +++ b/gen/go/headscale/v1/preauthkey.pb.go @@ -0,0 +1,596 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/preauthkey.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PreAuthKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + Reusable bool `protobuf:"varint,4,opt,name=reusable,proto3" json:"reusable,omitempty"` + Ephemeral bool `protobuf:"varint,5,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"` + Used bool `protobuf:"varint,6,opt,name=used,proto3" json:"used,omitempty"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expiration,proto3" json:"expiration,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + AclTags []string `protobuf:"bytes,9,rep,name=acl_tags,json=aclTags,proto3" json:"acl_tags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PreAuthKey) Reset() { + *x = PreAuthKey{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PreAuthKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreAuthKey) ProtoMessage() {} + +func (x *PreAuthKey) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PreAuthKey.ProtoReflect.Descriptor instead. +func (*PreAuthKey) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{0} +} + +func (x *PreAuthKey) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +func (x *PreAuthKey) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *PreAuthKey) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PreAuthKey) GetReusable() bool { + if x != nil { + return x.Reusable + } + return false +} + +func (x *PreAuthKey) GetEphemeral() bool { + if x != nil { + return x.Ephemeral + } + return false +} + +func (x *PreAuthKey) GetUsed() bool { + if x != nil { + return x.Used + } + return false +} + +func (x *PreAuthKey) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +func (x *PreAuthKey) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *PreAuthKey) GetAclTags() []string { + if x != nil { + return x.AclTags + } + return nil +} + +type CreatePreAuthKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + User uint64 `protobuf:"varint,1,opt,name=user,proto3" json:"user,omitempty"` + Reusable bool `protobuf:"varint,2,opt,name=reusable,proto3" json:"reusable,omitempty"` + Ephemeral bool `protobuf:"varint,3,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expiration,proto3" json:"expiration,omitempty"` + AclTags []string `protobuf:"bytes,5,rep,name=acl_tags,json=aclTags,proto3" json:"acl_tags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePreAuthKeyRequest) Reset() { + *x = CreatePreAuthKeyRequest{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePreAuthKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePreAuthKeyRequest) ProtoMessage() {} + +func (x *CreatePreAuthKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePreAuthKeyRequest.ProtoReflect.Descriptor instead. +func (*CreatePreAuthKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{1} +} + +func (x *CreatePreAuthKeyRequest) GetUser() uint64 { + if x != nil { + return x.User + } + return 0 +} + +func (x *CreatePreAuthKeyRequest) GetReusable() bool { + if x != nil { + return x.Reusable + } + return false +} + +func (x *CreatePreAuthKeyRequest) GetEphemeral() bool { + if x != nil { + return x.Ephemeral + } + return false +} + +func (x *CreatePreAuthKeyRequest) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +func (x *CreatePreAuthKeyRequest) GetAclTags() []string { + if x != nil { + return x.AclTags + } + return nil +} + +type CreatePreAuthKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PreAuthKey *PreAuthKey `protobuf:"bytes,1,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePreAuthKeyResponse) Reset() { + *x = CreatePreAuthKeyResponse{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePreAuthKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePreAuthKeyResponse) ProtoMessage() {} + +func (x *CreatePreAuthKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePreAuthKeyResponse.ProtoReflect.Descriptor instead. +func (*CreatePreAuthKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{2} +} + +func (x *CreatePreAuthKeyResponse) GetPreAuthKey() *PreAuthKey { + if x != nil { + return x.PreAuthKey + } + return nil +} + +type ExpirePreAuthKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpirePreAuthKeyRequest) Reset() { + *x = ExpirePreAuthKeyRequest{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpirePreAuthKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpirePreAuthKeyRequest) ProtoMessage() {} + +func (x *ExpirePreAuthKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpirePreAuthKeyRequest.ProtoReflect.Descriptor instead. +func (*ExpirePreAuthKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{3} +} + +func (x *ExpirePreAuthKeyRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +type ExpirePreAuthKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpirePreAuthKeyResponse) Reset() { + *x = ExpirePreAuthKeyResponse{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpirePreAuthKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpirePreAuthKeyResponse) ProtoMessage() {} + +func (x *ExpirePreAuthKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpirePreAuthKeyResponse.ProtoReflect.Descriptor instead. +func (*ExpirePreAuthKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{4} +} + +type DeletePreAuthKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePreAuthKeyRequest) Reset() { + *x = DeletePreAuthKeyRequest{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePreAuthKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePreAuthKeyRequest) ProtoMessage() {} + +func (x *DeletePreAuthKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePreAuthKeyRequest.ProtoReflect.Descriptor instead. +func (*DeletePreAuthKeyRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{5} +} + +func (x *DeletePreAuthKeyRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +type DeletePreAuthKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePreAuthKeyResponse) Reset() { + *x = DeletePreAuthKeyResponse{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePreAuthKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePreAuthKeyResponse) ProtoMessage() {} + +func (x *DeletePreAuthKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePreAuthKeyResponse.ProtoReflect.Descriptor instead. +func (*DeletePreAuthKeyResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{6} +} + +type ListPreAuthKeysRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPreAuthKeysRequest) Reset() { + *x = ListPreAuthKeysRequest{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPreAuthKeysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPreAuthKeysRequest) ProtoMessage() {} + +func (x *ListPreAuthKeysRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPreAuthKeysRequest.ProtoReflect.Descriptor instead. +func (*ListPreAuthKeysRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{7} +} + +type ListPreAuthKeysResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PreAuthKeys []*PreAuthKey `protobuf:"bytes,1,rep,name=pre_auth_keys,json=preAuthKeys,proto3" json:"pre_auth_keys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPreAuthKeysResponse) Reset() { + *x = ListPreAuthKeysResponse{} + mi := &file_headscale_v1_preauthkey_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPreAuthKeysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPreAuthKeysResponse) ProtoMessage() {} + +func (x *ListPreAuthKeysResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_preauthkey_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPreAuthKeysResponse.ProtoReflect.Descriptor instead. +func (*ListPreAuthKeysResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{8} +} + +func (x *ListPreAuthKeysResponse) GetPreAuthKeys() []*PreAuthKey { + if x != nil { + return x.PreAuthKeys + } + return nil +} + +var File_headscale_v1_preauthkey_proto protoreflect.FileDescriptor + +const file_headscale_v1_preauthkey_proto_rawDesc = "" + + "\n" + + "\x1dheadscale/v1/preauthkey.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x17headscale/v1/user.proto\"\xb6\x02\n" + + "\n" + + "PreAuthKey\x12&\n" + + "\x04user\x18\x01 \x01(\v2\x12.headscale.v1.UserR\x04user\x12\x0e\n" + + "\x02id\x18\x02 \x01(\x04R\x02id\x12\x10\n" + + "\x03key\x18\x03 \x01(\tR\x03key\x12\x1a\n" + + "\breusable\x18\x04 \x01(\bR\breusable\x12\x1c\n" + + "\tephemeral\x18\x05 \x01(\bR\tephemeral\x12\x12\n" + + "\x04used\x18\x06 \x01(\bR\x04used\x12:\n" + + "\n" + + "expiration\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "expiration\x129\n" + + "\n" + + "created_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x19\n" + + "\bacl_tags\x18\t \x03(\tR\aaclTags\"\xbe\x01\n" + + "\x17CreatePreAuthKeyRequest\x12\x12\n" + + "\x04user\x18\x01 \x01(\x04R\x04user\x12\x1a\n" + + "\breusable\x18\x02 \x01(\bR\breusable\x12\x1c\n" + + "\tephemeral\x18\x03 \x01(\bR\tephemeral\x12:\n" + + "\n" + + "expiration\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "expiration\x12\x19\n" + + "\bacl_tags\x18\x05 \x03(\tR\aaclTags\"V\n" + + "\x18CreatePreAuthKeyResponse\x12:\n" + + "\fpre_auth_key\x18\x01 \x01(\v2\x18.headscale.v1.PreAuthKeyR\n" + + "preAuthKey\")\n" + + "\x17ExpirePreAuthKeyRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\"\x1a\n" + + "\x18ExpirePreAuthKeyResponse\")\n" + + "\x17DeletePreAuthKeyRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\"\x1a\n" + + "\x18DeletePreAuthKeyResponse\"\x18\n" + + "\x16ListPreAuthKeysRequest\"W\n" + + "\x17ListPreAuthKeysResponse\x12<\n" + + "\rpre_auth_keys\x18\x01 \x03(\v2\x18.headscale.v1.PreAuthKeyR\vpreAuthKeysB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_preauthkey_proto_rawDescOnce sync.Once + file_headscale_v1_preauthkey_proto_rawDescData []byte +) + +func file_headscale_v1_preauthkey_proto_rawDescGZIP() []byte { + file_headscale_v1_preauthkey_proto_rawDescOnce.Do(func() { + file_headscale_v1_preauthkey_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_preauthkey_proto_rawDesc), len(file_headscale_v1_preauthkey_proto_rawDesc))) + }) + return file_headscale_v1_preauthkey_proto_rawDescData +} + +var file_headscale_v1_preauthkey_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_headscale_v1_preauthkey_proto_goTypes = []any{ + (*PreAuthKey)(nil), // 0: headscale.v1.PreAuthKey + (*CreatePreAuthKeyRequest)(nil), // 1: headscale.v1.CreatePreAuthKeyRequest + (*CreatePreAuthKeyResponse)(nil), // 2: headscale.v1.CreatePreAuthKeyResponse + (*ExpirePreAuthKeyRequest)(nil), // 3: headscale.v1.ExpirePreAuthKeyRequest + (*ExpirePreAuthKeyResponse)(nil), // 4: headscale.v1.ExpirePreAuthKeyResponse + (*DeletePreAuthKeyRequest)(nil), // 5: headscale.v1.DeletePreAuthKeyRequest + (*DeletePreAuthKeyResponse)(nil), // 6: headscale.v1.DeletePreAuthKeyResponse + (*ListPreAuthKeysRequest)(nil), // 7: headscale.v1.ListPreAuthKeysRequest + (*ListPreAuthKeysResponse)(nil), // 8: headscale.v1.ListPreAuthKeysResponse + (*User)(nil), // 9: headscale.v1.User + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp +} +var file_headscale_v1_preauthkey_proto_depIdxs = []int32{ + 9, // 0: headscale.v1.PreAuthKey.user:type_name -> headscale.v1.User + 10, // 1: headscale.v1.PreAuthKey.expiration:type_name -> google.protobuf.Timestamp + 10, // 2: headscale.v1.PreAuthKey.created_at:type_name -> google.protobuf.Timestamp + 10, // 3: headscale.v1.CreatePreAuthKeyRequest.expiration:type_name -> google.protobuf.Timestamp + 0, // 4: headscale.v1.CreatePreAuthKeyResponse.pre_auth_key:type_name -> headscale.v1.PreAuthKey + 0, // 5: headscale.v1.ListPreAuthKeysResponse.pre_auth_keys:type_name -> headscale.v1.PreAuthKey + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_headscale_v1_preauthkey_proto_init() } +func file_headscale_v1_preauthkey_proto_init() { + if File_headscale_v1_preauthkey_proto != nil { + return + } + file_headscale_v1_user_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_preauthkey_proto_rawDesc), len(file_headscale_v1_preauthkey_proto_rawDesc)), + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_preauthkey_proto_goTypes, + DependencyIndexes: file_headscale_v1_preauthkey_proto_depIdxs, + MessageInfos: file_headscale_v1_preauthkey_proto_msgTypes, + }.Build() + File_headscale_v1_preauthkey_proto = out.File + file_headscale_v1_preauthkey_proto_goTypes = nil + file_headscale_v1_preauthkey_proto_depIdxs = nil +} diff --git a/gen/go/headscale/v1/user.pb.go b/gen/go/headscale/v1/user.pb.go new file mode 100644 index 000000000..5f05d0849 --- /dev/null +++ b/gen/go/headscale/v1/user.pb.go @@ -0,0 +1,615 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: headscale/v1/user.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type User struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + DisplayName string `protobuf:"bytes,4,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + ProviderId string `protobuf:"bytes,6,opt,name=provider_id,json=providerId,proto3" json:"provider_id,omitempty"` + Provider string `protobuf:"bytes,7,opt,name=provider,proto3" json:"provider,omitempty"` + ProfilePicUrl string `protobuf:"bytes,8,opt,name=profile_pic_url,json=profilePicUrl,proto3" json:"profile_pic_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *User) Reset() { + *x = User{} + mi := &file_headscale_v1_user_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{0} +} + +func (x *User) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *User) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *User) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *User) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *User) GetProviderId() string { + if x != nil { + return x.ProviderId + } + return "" +} + +func (x *User) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *User) GetProfilePicUrl() string { + if x != nil { + return x.ProfilePicUrl + } + return "" +} + +type CreateUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + PictureUrl string `protobuf:"bytes,4,opt,name=picture_url,json=pictureUrl,proto3" json:"picture_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateUserRequest) Reset() { + *x = CreateUserRequest{} + mi := &file_headscale_v1_user_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserRequest) ProtoMessage() {} + +func (x *CreateUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead. +func (*CreateUserRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateUserRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateUserRequest) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *CreateUserRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *CreateUserRequest) GetPictureUrl() string { + if x != nil { + return x.PictureUrl + } + return "" +} + +type CreateUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateUserResponse) Reset() { + *x = CreateUserResponse{} + mi := &file_headscale_v1_user_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserResponse) ProtoMessage() {} + +func (x *CreateUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserResponse.ProtoReflect.Descriptor instead. +func (*CreateUserResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateUserResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +type RenameUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OldId uint64 `protobuf:"varint,1,opt,name=old_id,json=oldId,proto3" json:"old_id,omitempty"` + NewName string `protobuf:"bytes,2,opt,name=new_name,json=newName,proto3" json:"new_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RenameUserRequest) Reset() { + *x = RenameUserRequest{} + mi := &file_headscale_v1_user_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RenameUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RenameUserRequest) ProtoMessage() {} + +func (x *RenameUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RenameUserRequest.ProtoReflect.Descriptor instead. +func (*RenameUserRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{3} +} + +func (x *RenameUserRequest) GetOldId() uint64 { + if x != nil { + return x.OldId + } + return 0 +} + +func (x *RenameUserRequest) GetNewName() string { + if x != nil { + return x.NewName + } + return "" +} + +type RenameUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RenameUserResponse) Reset() { + *x = RenameUserResponse{} + mi := &file_headscale_v1_user_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RenameUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RenameUserResponse) ProtoMessage() {} + +func (x *RenameUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RenameUserResponse.ProtoReflect.Descriptor instead. +func (*RenameUserResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{4} +} + +func (x *RenameUserResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +type DeleteUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteUserRequest) Reset() { + *x = DeleteUserRequest{} + mi := &file_headscale_v1_user_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserRequest) ProtoMessage() {} + +func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead. +func (*DeleteUserRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteUserRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +type DeleteUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteUserResponse) Reset() { + *x = DeleteUserResponse{} + mi := &file_headscale_v1_user_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserResponse) ProtoMessage() {} + +func (x *DeleteUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserResponse.ProtoReflect.Descriptor instead. +func (*DeleteUserResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{6} +} + +type ListUsersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListUsersRequest) Reset() { + *x = ListUsersRequest{} + mi := &file_headscale_v1_user_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListUsersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersRequest) ProtoMessage() {} + +func (x *ListUsersRequest) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersRequest.ProtoReflect.Descriptor instead. +func (*ListUsersRequest) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{7} +} + +func (x *ListUsersRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ListUsersRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListUsersRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +type ListUsersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Users []*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListUsersResponse) Reset() { + *x = ListUsersResponse{} + mi := &file_headscale_v1_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListUsersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUsersResponse) ProtoMessage() {} + +func (x *ListUsersResponse) ProtoReflect() protoreflect.Message { + mi := &file_headscale_v1_user_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUsersResponse.ProtoReflect.Descriptor instead. +func (*ListUsersResponse) Descriptor() ([]byte, []int) { + return file_headscale_v1_user_proto_rawDescGZIP(), []int{8} +} + +func (x *ListUsersResponse) GetUsers() []*User { + if x != nil { + return x.Users + } + return nil +} + +var File_headscale_v1_user_proto protoreflect.FileDescriptor + +const file_headscale_v1_user_proto_rawDesc = "" + + "\n" + + "\x17headscale/v1/user.proto\x12\fheadscale.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x83\x02\n" + + "\x04User\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x129\n" + + "\n" + + "created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12!\n" + + "\fdisplay_name\x18\x04 \x01(\tR\vdisplayName\x12\x14\n" + + "\x05email\x18\x05 \x01(\tR\x05email\x12\x1f\n" + + "\vprovider_id\x18\x06 \x01(\tR\n" + + "providerId\x12\x1a\n" + + "\bprovider\x18\a \x01(\tR\bprovider\x12&\n" + + "\x0fprofile_pic_url\x18\b \x01(\tR\rprofilePicUrl\"\x81\x01\n" + + "\x11CreateUserRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12!\n" + + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\x12\x14\n" + + "\x05email\x18\x03 \x01(\tR\x05email\x12\x1f\n" + + "\vpicture_url\x18\x04 \x01(\tR\n" + + "pictureUrl\"<\n" + + "\x12CreateUserResponse\x12&\n" + + "\x04user\x18\x01 \x01(\v2\x12.headscale.v1.UserR\x04user\"E\n" + + "\x11RenameUserRequest\x12\x15\n" + + "\x06old_id\x18\x01 \x01(\x04R\x05oldId\x12\x19\n" + + "\bnew_name\x18\x02 \x01(\tR\anewName\"<\n" + + "\x12RenameUserResponse\x12&\n" + + "\x04user\x18\x01 \x01(\v2\x12.headscale.v1.UserR\x04user\"#\n" + + "\x11DeleteUserRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\"\x14\n" + + "\x12DeleteUserResponse\"L\n" + + "\x10ListUsersRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05email\x18\x03 \x01(\tR\x05email\"=\n" + + "\x11ListUsersResponse\x12(\n" + + "\x05users\x18\x01 \x03(\v2\x12.headscale.v1.UserR\x05usersB)Z'github.com/juanfont/headscale/gen/go/v1b\x06proto3" + +var ( + file_headscale_v1_user_proto_rawDescOnce sync.Once + file_headscale_v1_user_proto_rawDescData []byte +) + +func file_headscale_v1_user_proto_rawDescGZIP() []byte { + file_headscale_v1_user_proto_rawDescOnce.Do(func() { + file_headscale_v1_user_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_headscale_v1_user_proto_rawDesc), len(file_headscale_v1_user_proto_rawDesc))) + }) + return file_headscale_v1_user_proto_rawDescData +} + +var file_headscale_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_headscale_v1_user_proto_goTypes = []any{ + (*User)(nil), // 0: headscale.v1.User + (*CreateUserRequest)(nil), // 1: headscale.v1.CreateUserRequest + (*CreateUserResponse)(nil), // 2: headscale.v1.CreateUserResponse + (*RenameUserRequest)(nil), // 3: headscale.v1.RenameUserRequest + (*RenameUserResponse)(nil), // 4: headscale.v1.RenameUserResponse + (*DeleteUserRequest)(nil), // 5: headscale.v1.DeleteUserRequest + (*DeleteUserResponse)(nil), // 6: headscale.v1.DeleteUserResponse + (*ListUsersRequest)(nil), // 7: headscale.v1.ListUsersRequest + (*ListUsersResponse)(nil), // 8: headscale.v1.ListUsersResponse + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp +} +var file_headscale_v1_user_proto_depIdxs = []int32{ + 9, // 0: headscale.v1.User.created_at:type_name -> google.protobuf.Timestamp + 0, // 1: headscale.v1.CreateUserResponse.user:type_name -> headscale.v1.User + 0, // 2: headscale.v1.RenameUserResponse.user:type_name -> headscale.v1.User + 0, // 3: headscale.v1.ListUsersResponse.users:type_name -> headscale.v1.User + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_headscale_v1_user_proto_init() } +func file_headscale_v1_user_proto_init() { + if File_headscale_v1_user_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_headscale_v1_user_proto_rawDesc), len(file_headscale_v1_user_proto_rawDesc)), + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_headscale_v1_user_proto_goTypes, + DependencyIndexes: file_headscale_v1_user_proto_depIdxs, + MessageInfos: file_headscale_v1_user_proto_msgTypes, + }.Build() + File_headscale_v1_user_proto = out.File + file_headscale_v1_user_proto_goTypes = nil + file_headscale_v1_user_proto_depIdxs = nil +} diff --git a/gen/openapiv2/headscale/v1/apikey.swagger.json b/gen/openapiv2/headscale/v1/apikey.swagger.json new file mode 100644 index 000000000..8c8596a95 --- /dev/null +++ b/gen/openapiv2/headscale/v1/apikey.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/apikey.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/auth.swagger.json b/gen/openapiv2/headscale/v1/auth.swagger.json new file mode 100644 index 000000000..2e99e1a75 --- /dev/null +++ b/gen/openapiv2/headscale/v1/auth.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/auth.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/device.swagger.json b/gen/openapiv2/headscale/v1/device.swagger.json new file mode 100644 index 000000000..99d20debf --- /dev/null +++ b/gen/openapiv2/headscale/v1/device.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/device.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json new file mode 100644 index 000000000..545cf0b5b --- /dev/null +++ b/gen/openapiv2/headscale/v1/headscale.swagger.json @@ -0,0 +1,1535 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/headscale.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "HeadscaleService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/api/v1/apikey": { + "get": { + "operationId": "HeadscaleService_ListApiKeys", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListApiKeysResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + }, + "post": { + "summary": "--- ApiKeys start ---", + "operationId": "HeadscaleService_CreateApiKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateApiKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateApiKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/apikey/expire": { + "post": { + "operationId": "HeadscaleService_ExpireApiKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExpireApiKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ExpireApiKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/apikey/{prefix}": { + "delete": { + "operationId": "HeadscaleService_DeleteApiKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteApiKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "prefix", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "id", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/auth/approve": { + "post": { + "operationId": "HeadscaleService_AuthApprove", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1AuthApproveResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1AuthApproveRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/auth/register": { + "post": { + "summary": "--- Auth start ---", + "operationId": "HeadscaleService_AuthRegister", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1AuthRegisterResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1AuthRegisterRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/auth/reject": { + "post": { + "operationId": "HeadscaleService_AuthReject", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1AuthRejectResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1AuthRejectRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/debug/node": { + "post": { + "summary": "--- Node start ---", + "operationId": "HeadscaleService_DebugCreateNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DebugCreateNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1DebugCreateNodeRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/health": { + "get": { + "summary": "--- Health start ---", + "operationId": "HeadscaleService_Health", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1HealthResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node": { + "get": { + "operationId": "HeadscaleService_ListNodes", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListNodesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "user", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/backfillips": { + "post": { + "operationId": "HeadscaleService_BackfillNodeIPs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1BackfillNodeIPsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "confirmed", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/register": { + "post": { + "operationId": "HeadscaleService_RegisterNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RegisterNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "user", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "key", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/{nodeId}": { + "get": { + "operationId": "HeadscaleService_GetNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + }, + "delete": { + "operationId": "HeadscaleService_DeleteNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/{nodeId}/approve_routes": { + "post": { + "operationId": "HeadscaleService_SetApprovedRoutes", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SetApprovedRoutesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/HeadscaleServiceSetApprovedRoutesBody" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/{nodeId}/expire": { + "post": { + "operationId": "HeadscaleService_ExpireNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExpireNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + }, + { + "name": "expiry", + "in": "query", + "required": false, + "type": "string", + "format": "date-time" + }, + { + "name": "disableExpiry", + "description": "When true, sets expiry to null (node will never expire).", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/{nodeId}/rename/{newName}": { + "post": { + "operationId": "HeadscaleService_RenameNode", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RenameNodeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + }, + { + "name": "newName", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/node/{nodeId}/tags": { + "post": { + "operationId": "HeadscaleService_SetTags", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SetTagsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "nodeId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/HeadscaleServiceSetTagsBody" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/policy": { + "get": { + "summary": "--- Policy start ---", + "operationId": "HeadscaleService_GetPolicy", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetPolicyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + }, + "put": { + "operationId": "HeadscaleService_SetPolicy", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SetPolicyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1SetPolicyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/policy/check": { + "post": { + "operationId": "HeadscaleService_CheckPolicy", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CheckPolicyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CheckPolicyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/preauthkey": { + "get": { + "operationId": "HeadscaleService_ListPreAuthKeys", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListPreAuthKeysResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "HeadscaleService" + ] + }, + "delete": { + "operationId": "HeadscaleService_DeletePreAuthKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeletePreAuthKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + }, + "post": { + "summary": "--- PreAuthKeys start ---", + "operationId": "HeadscaleService_CreatePreAuthKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreatePreAuthKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreatePreAuthKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/preauthkey/expire": { + "post": { + "operationId": "HeadscaleService_ExpirePreAuthKey", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExpirePreAuthKeyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ExpirePreAuthKeyRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/user": { + "get": { + "operationId": "HeadscaleService_ListUsers", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListUsersResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + }, + { + "name": "name", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "email", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "HeadscaleService" + ] + }, + "post": { + "summary": "--- User start ---", + "operationId": "HeadscaleService_CreateUser", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateUserResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateUserRequest" + } + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/user/{id}": { + "delete": { + "operationId": "HeadscaleService_DeleteUser", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteUserResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + } + }, + "/api/v1/user/{oldId}/rename/{newName}": { + "post": { + "operationId": "HeadscaleService_RenameUser", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RenameUserResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "oldId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + }, + { + "name": "newName", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "HeadscaleService" + ] + } + } + }, + "definitions": { + "HeadscaleServiceSetApprovedRoutesBody": { + "type": "object", + "properties": { + "routes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "HeadscaleServiceSetTagsBody": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1ApiKey": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uint64" + }, + "prefix": { + "type": "string" + }, + "expiration": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "lastSeen": { + "type": "string", + "format": "date-time" + } + } + }, + "v1AuthApproveRequest": { + "type": "object", + "properties": { + "authId": { + "type": "string" + } + } + }, + "v1AuthApproveResponse": { + "type": "object" + }, + "v1AuthRegisterRequest": { + "type": "object", + "properties": { + "user": { + "type": "string" + }, + "authId": { + "type": "string" + } + } + }, + "v1AuthRegisterResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1AuthRejectRequest": { + "type": "object", + "properties": { + "authId": { + "type": "string" + } + } + }, + "v1AuthRejectResponse": { + "type": "object" + }, + "v1BackfillNodeIPsResponse": { + "type": "object", + "properties": { + "changes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1CheckPolicyRequest": { + "type": "object", + "properties": { + "policy": { + "type": "string" + } + } + }, + "v1CheckPolicyResponse": { + "type": "object" + }, + "v1CreateApiKeyRequest": { + "type": "object", + "properties": { + "expiration": { + "type": "string", + "format": "date-time" + } + } + }, + "v1CreateApiKeyResponse": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + } + } + }, + "v1CreatePreAuthKeyRequest": { + "type": "object", + "properties": { + "user": { + "type": "string", + "format": "uint64" + }, + "reusable": { + "type": "boolean" + }, + "ephemeral": { + "type": "boolean" + }, + "expiration": { + "type": "string", + "format": "date-time" + }, + "aclTags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1CreatePreAuthKeyResponse": { + "type": "object", + "properties": { + "preAuthKey": { + "$ref": "#/definitions/v1PreAuthKey" + } + } + }, + "v1CreateUserRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "pictureUrl": { + "type": "string" + } + } + }, + "v1CreateUserResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/v1User" + } + } + }, + "v1DebugCreateNodeRequest": { + "type": "object", + "properties": { + "user": { + "type": "string" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "routes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1DebugCreateNodeResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1DeleteApiKeyResponse": { + "type": "object" + }, + "v1DeleteNodeResponse": { + "type": "object" + }, + "v1DeletePreAuthKeyResponse": { + "type": "object" + }, + "v1DeleteUserResponse": { + "type": "object" + }, + "v1ExpireApiKeyRequest": { + "type": "object", + "properties": { + "prefix": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uint64" + } + } + }, + "v1ExpireApiKeyResponse": { + "type": "object" + }, + "v1ExpireNodeResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1ExpirePreAuthKeyRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uint64" + } + } + }, + "v1ExpirePreAuthKeyResponse": { + "type": "object" + }, + "v1GetNodeResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1GetPolicyResponse": { + "type": "object", + "properties": { + "policy": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "v1HealthResponse": { + "type": "object", + "properties": { + "databaseConnectivity": { + "type": "boolean" + } + } + }, + "v1ListApiKeysResponse": { + "type": "object", + "properties": { + "apiKeys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ApiKey" + } + } + } + }, + "v1ListNodesResponse": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Node" + } + } + } + }, + "v1ListPreAuthKeysResponse": { + "type": "object", + "properties": { + "preAuthKeys": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1PreAuthKey" + } + } + } + }, + "v1ListUsersResponse": { + "type": "object", + "properties": { + "users": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1User" + } + } + } + }, + "v1Node": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uint64" + }, + "machineKey": { + "type": "string" + }, + "nodeKey": { + "type": "string" + }, + "discoKey": { + "type": "string" + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/v1User" + }, + "lastSeen": { + "type": "string", + "format": "date-time" + }, + "expiry": { + "type": "string", + "format": "date-time" + }, + "preAuthKey": { + "$ref": "#/definitions/v1PreAuthKey" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "registerMethod": { + "$ref": "#/definitions/v1RegisterMethod" + }, + "givenName": { + "type": "string", + "title": "Deprecated\nrepeated string forced_tags = 18;\nrepeated string invalid_tags = 19;\nrepeated string valid_tags = 20;" + }, + "online": { + "type": "boolean" + }, + "approvedRoutes": { + "type": "array", + "items": { + "type": "string" + } + }, + "availableRoutes": { + "type": "array", + "items": { + "type": "string" + } + }, + "subnetRoutes": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1PreAuthKey": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/v1User" + }, + "id": { + "type": "string", + "format": "uint64" + }, + "key": { + "type": "string" + }, + "reusable": { + "type": "boolean" + }, + "ephemeral": { + "type": "boolean" + }, + "used": { + "type": "boolean" + }, + "expiration": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "aclTags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1RegisterMethod": { + "type": "string", + "enum": [ + "REGISTER_METHOD_UNSPECIFIED", + "REGISTER_METHOD_AUTH_KEY", + "REGISTER_METHOD_CLI", + "REGISTER_METHOD_OIDC" + ], + "default": "REGISTER_METHOD_UNSPECIFIED" + }, + "v1RegisterNodeResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1RenameNodeResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1RenameUserResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/v1User" + } + } + }, + "v1SetApprovedRoutesResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1SetPolicyRequest": { + "type": "object", + "properties": { + "policy": { + "type": "string" + } + } + }, + "v1SetPolicyResponse": { + "type": "object", + "properties": { + "policy": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "v1SetTagsResponse": { + "type": "object", + "properties": { + "node": { + "$ref": "#/definitions/v1Node" + } + } + }, + "v1User": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uint64" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "displayName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "providerId": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "profilePicUrl": { + "type": "string" + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/node.swagger.json b/gen/openapiv2/headscale/v1/node.swagger.json new file mode 100644 index 000000000..163213470 --- /dev/null +++ b/gen/openapiv2/headscale/v1/node.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/node.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/policy.swagger.json b/gen/openapiv2/headscale/v1/policy.swagger.json new file mode 100644 index 000000000..63057ed0e --- /dev/null +++ b/gen/openapiv2/headscale/v1/policy.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/policy.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/preauthkey.swagger.json b/gen/openapiv2/headscale/v1/preauthkey.swagger.json new file mode 100644 index 000000000..17a2be1a7 --- /dev/null +++ b/gen/openapiv2/headscale/v1/preauthkey.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/preauthkey.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapiv2/headscale/v1/user.swagger.json b/gen/openapiv2/headscale/v1/user.swagger.json new file mode 100644 index 000000000..008ca3e85 --- /dev/null +++ b/gen/openapiv2/headscale/v1/user.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "headscale/v1/user.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/go.mod b/go.mod index 41cf5b057..ce1dc52f1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/juanfont/headscale -go 1.26.4 +go 1.26.2 require ( github.com/arl/statsviz v0.8.0 @@ -8,52 +8,54 @@ require ( github.com/chasefleming/elem-go v0.31.0 github.com/coder/websocket v1.8.14 github.com/coreos/go-oidc/v3 v3.18.0 - github.com/creachadair/command v0.2.6 - github.com/creachadair/flax v0.0.6 - github.com/danielgtaylor/huma/v2 v2.38.0 + github.com/creachadair/command v0.2.2 + github.com/creachadair/flax v0.0.5 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docker/docker v28.5.2+incompatible - github.com/fsnotify/fsnotify v1.10.1 + github.com/fsnotify/fsnotify v1.9.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-chi/chi/v5 v5.3.0 + github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/metrics v0.1.1 - github.com/go-gormigrate/gormigrate/v2 v2.1.6 - github.com/go-json-experiment/json v0.0.0-20260601182631-00ed12fed2a6 + github.com/go-gormigrate/gormigrate/v2 v2.1.5 + github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 github.com/gofrs/uuid/v5 v5.4.0 github.com/google/go-cmp v0.7.0 + github.com/gorilla/mux v1.8.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/jagottsicher/termcolor v1.0.2 - github.com/oapi-codegen/runtime v1.4.1 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/ory/dockertest/v3 v3.12.0 + github.com/philip-bui/grpc-zerolog v1.0.1 github.com/pkg/profile v1.7.0 github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.68.1 + github.com/prometheus/common v0.67.5 github.com/pterm/pterm v0.12.83 - github.com/puzpuzpuz/xsync/v4 v4.5.0 - github.com/realclientip/realclientip-go v1.0.0 - github.com/rs/zerolog v1.35.1 + github.com/puzpuzpuz/xsync/v4 v4.4.0 + github.com/rs/zerolog v1.35.0 github.com/samber/lo v1.53.0 github.com/sasha-s/go-deadlock v0.3.9 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd - github.com/tailscale/squibble v0.0.0-20260411062017-141f5d618bc4 - github.com/tailscale/tailsql v0.0.0-20260522170732-77aec5aabc76 + github.com/tailscale/squibble v0.0.0-20260303070345-3ac5157f405e + github.com/tailscale/tailsql v0.0.0-20260322172246-3ab0c1744d9c github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.53.0 - golang.org/x/exp v0.0.0-20260603202125-055de637280b - golang.org/x/net v0.56.0 + golang.org/x/crypto v0.50.0 + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 + golang.org/x/net v0.53.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.21.0 + golang.org/x/sync v0.20.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.1 - pgregory.net/rapid v1.3.0 - tailscale.com v1.101.0-pre - tailscale.com/client/tailscale/v2 v2.9.0 + pgregory.net/rapid v1.2.0 + tailscale.com v1.97.0-pre.0.20260429005429-40088602c960 zombiezen.com/go/postgrestest v1.0.1 ) @@ -75,10 +77,10 @@ require ( // together, e.g: // go get modernc.org/libc@v1.55.3 modernc.org/sqlite@v1.33.1 require ( - modernc.org/libc v1.72.3 // indirect + modernc.org/libc v1.70.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.52.0 + modernc.org/sqlite v1.48.2 ) // NOTE: gvisor must be updated in lockstep with @@ -91,7 +93,7 @@ require gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect require ( atomicgo.dev/cursor v0.2.0 // indirect - atomicgo.dev/keyboard v0.2.10 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.2.0 // indirect @@ -102,7 +104,6 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect @@ -125,50 +126,49 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/containerd/console v1.0.5 // indirect - github.com/containerd/continuity v0.5.0 // indirect + github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/creachadair/mds v0.29.0 // indirect + github.com/creachadair/mds v0.26.2 // indirect github.com/creachadair/msync v0.8.2 // indirect github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.5.3+incompatible // indirect - github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/cli v29.4.0+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fogleman/gg v1.3.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gaissmai/bart v0.26.1 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.5 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-querystring v1.2.0 // indirect - github.com/google/pprof v0.0.0-20260604005048-7023385849c0 // indirect + github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gookit/color v1.6.1 // indirect + github.com/gookit/color v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect - github.com/hashicorp/go-version v1.9.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/pgx/v5 v5.8.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -176,18 +176,18 @@ require ( github.com/jsimonetti/rtnetlink v1.4.2 // indirect github.com/kamstrup/intmap v0.5.2 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.18.6 // indirect - github.com/lib/pq v1.12.3 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/lib/pq v1.11.1 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect - github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect - github.com/mattn/go-runewidth v0.0.24 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.20 // indirect github.com/mdlayher/netlink v1.8.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/moby/api v1.54.2 // indirect - github.com/moby/moby/client v0.4.1 // indirect + github.com/moby/moby/api v1.54.1 // indirect + github.com/moby/moby/client v0.4.0 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/term v0.5.2 // indirect @@ -197,14 +197,15 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runc v1.3.2 // indirect - github.com/pelletier/go-toml/v2 v2.3.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect - github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect + github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect github.com/pires/go-proxyproto v0.9.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus-community/pro-bing v0.7.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.20.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/safchain/ethtool v0.7.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect @@ -217,9 +218,9 @@ require ( github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect - github.com/tailscale/setec v0.0.0-20260310221408-dcd97e42f251 // indirect + github.com/tailscale/setec v0.0.0-20260115174028-19d190c5556d // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368 // indirect - github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad // indirect + github.com/tailscale/wireguard-go v0.0.0-20260304043104-4184faf59e56 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -227,28 +228,24 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.yaml.in/yaml/v2 v2.4.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/exp/typeparams v0.0.0-20260603202125-055de637280b // indirect - golang.org/x/image v0.41.0 // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/image v0.27.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.81.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect k8s.io/client-go v0.34.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect software.sslmate.com/src/go-pkcs12 v0.4.0 // indirect diff --git a/go.sum b/go.sum index 048eba2ca..eabebf535 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= -atomicgo.dev/keyboard v0.2.10 h1:v7mvUKUZLHIggxULEIuWbT+WkkyQSgdbA201EziAhHU= -atomicgo.dev/keyboard v0.2.10/go.mod h1:ap/z5ilnhLqYq852m6kPeTq5Z6aESGWu5mzRpJlC6aI= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -22,23 +22,28 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 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/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/arl/statsviz v0.8.0 h1:O6GjjVxEDxcByAucOSl29HaGYLXsuwA3ujJw8H9E7/U= github.com/arl/statsviz v0.8.0/go.mod h1:XlrbiT7xYT03xaW9JMMfD8KFUhBOESJwfyNJu83PbB0= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= @@ -85,7 +90,6 @@ github.com/axiomhq/hyperloglog v0.2.6 h1:sRhvvF3RIXWQgAXaTphLp4yJiX4S0IN3MWTaAgZ github.com/axiomhq/hyperloglog v0.2.6/go.mod h1:YjX/dQqCR/7QYX0g8mu8UZAjpIenz1FKM71UEsjFoTo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -109,10 +113,11 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= -github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 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= @@ -124,20 +129,18 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8 github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creachadair/command v0.2.6 h1:+d4xUSZza5nqRVVsVRf5jDbix/0uKxVqFrc30F4uyRI= -github.com/creachadair/command v0.2.6/go.mod h1:HWvS3zEpVakKlNXxT/j1U8tNoDaY7By7ORYQ+m1ha2U= -github.com/creachadair/flax v0.0.6 h1:IFUdRdfKynNTyR5SRlrHMUGnxxMZZen8RVpwcDq/ftk= -github.com/creachadair/flax v0.0.6/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8= -github.com/creachadair/mds v0.29.0 h1:LyR8pWAj2ofsrdJ9s2YoIqN8b6a1djHmPQULD2fTSCA= -github.com/creachadair/mds v0.29.0/go.mod h1:dMBTCSy3iS3dwh4Rb1zxeZz2d7K8+N24GCTsayWtQRI= +github.com/creachadair/command v0.2.2 h1:4RGsUhqFf1imFC+vMWOOCiQdncThCdcdMJp0JNCjxxc= +github.com/creachadair/command v0.2.2/go.mod h1:Z6Zp6CSJcnaWWR4wHgdqzODnFdxFJAaa/DrcVkeUu3E= +github.com/creachadair/flax v0.0.5 h1:zt+CRuXQASxwQ68e9GHAOnEgAU29nF0zYMHOCrL5wzE= +github.com/creachadair/flax v0.0.5/go.mod h1:F1PML0JZLXSNDMNiRGK2yjm5f+L9QCHchyHBldFymj8= +github.com/creachadair/mds v0.26.2 h1:rCtvEV/bCRY0hGfwvvMg0p3yzKgBE8l/9OV4fjF9QQ8= +github.com/creachadair/mds v0.26.2/go.mod h1:dMBTCSy3iS3dwh4Rb1zxeZz2d7K8+N24GCTsayWtQRI= github.com/creachadair/msync v0.8.2 h1:ujvc/SVJPn+bFwmjUHucXNTTn3opVe2YbQ46mBCnP08= github.com/creachadair/msync v0.8.2/go.mod h1:LzxqD9kfIl/O3DczkwOgJplLPqwrTbIhINlf9bHIsEY= 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.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/danielgtaylor/huma/v2 v2.38.0 h1:fb0WZCatnaiHLphMQDDWDjygNxfMkX/ENma3QsRl7vY= -github.com/danielgtaylor/huma/v2 v2.38.0/go.mod h1:k9hwjlgWFt1t2jsmQGlsgXAG2FBTZa4kkjV581qAtfo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -152,12 +155,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/docker/cli v29.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs= -github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM= +github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 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/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= -github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -171,10 +174,10 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= -github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= -github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo= github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= @@ -183,18 +186,18 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= -github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/metrics v0.1.1 h1:CXhbnkAVVjb0k73EBRQ6Z2YdWFnbXZgNtg1Mboguibk= github.com/go-chi/metrics v0.1.1/go.mod h1:mcGTM1pPalP7WCtb+akNYFO/lwNwBBLCuedepqjoPn4= -github.com/go-gormigrate/gormigrate/v2 v2.1.6 h1:VtX+l1Stj2v5RGubVQk0LS/8EPGXR+ldcOyCmlmKoyg= -github.com/go-gormigrate/gormigrate/v2 v2.1.6/go.mod h1:PZpedQc4tWaxn6kvXicwhinh3L0seLpMc5ReKRX5id4= -github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ= -github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8= +github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-json-experiment/json v0.0.0-20260601182631-00ed12fed2a6 h1:nxP4pPoyqOAgX8lYDFCfl3DyKeXErCvSvhcyzwGV9CE= -github.com/go-json-experiment/json v0.0.0-20260601182631-00ed12fed2a6/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= +github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao= +github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -206,8 +209,6 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689 h1:0psnKZ+N2IP43/SZC8SKx6OpFJwLmQb9m9QyV9BC2f8= -github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689/go.mod h1:OGmRfY/9QEK2P5zCRtmqfbCF283xPkU2dvVA4MvbvpI= github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= @@ -223,6 +224,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -239,22 +242,26 @@ github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdF github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20260604005048-7023385849c0 h1:h1QTMDl6q9wDvDCJVpKQSjgleGFYnd2fOxmg2K+6BGE= -github.com/google/pprof v0.0.0-20260604005048-7023385849c0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno= +github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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/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/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= -github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU= -github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= +github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= -github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= -github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= @@ -273,8 +280,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jagottsicher/termcolor v1.0.2 h1:fo0c51pQSuLBN1+yVX2ZE+hE+P7ULb/TY8eRowJnrsM= @@ -292,38 +299,44 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kamstrup/intmap v0.5.2 h1:qnwBm1mh4XAnW9W9Ue9tZtTff8pS6+s6iKF6JRIV2Dk= github.com/kamstrup/intmap v0.5.2/go.mod h1:gWUVWHKzWj8xpJVFf5GC0O26bWmv3GqdnIX/LMT6Aq4= 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.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/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= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= -github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= +github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= -github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= -github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 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.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5YyvvlM= @@ -338,10 +351,10 @@ 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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= -github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= -github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= +github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= +github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= 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= @@ -358,10 +371,6 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= -github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= -github.com/oapi-codegen/runtime v1.4.1 h1:9nwLoI+KrWxzbBcp0jO/R8uXqbik/HUyCvPeU68Y/qo= -github.com/oapi-codegen/runtime v1.4.1/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk= github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -373,13 +382,15 @@ github.com/opencontainers/runc v1.3.2/go.mod h1:F7UQQEsxcjUNnFpT1qPLHZBKYP7yWwk6 github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= -github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= -github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= -github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA= +github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ= 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.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U= @@ -393,26 +404,34 @@ github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Q github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-community/pro-bing v0.7.0 h1:KFYFbxC2f2Fp6c+TyxbCOEarf7rbnzr9Gw8eIb0RfZA= +github.com/prometheus-community/pro-bing v0.7.0/go.mod h1:Moob9dvlY50Bfq6i88xIwfyw7xLFHH69LUgx9n5zqCE= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= -github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= github.com/pterm/pterm v0.12.83 h1:ie+YmGmA727VuhxBlyGr74Ks+7McV6kT99IB8EU80aA= github.com/pterm/pterm v0.12.83/go.mod h1:xlgc6bFWyJIMtmLJvGim+L7jhSReilOlOnodeIYe4Tk= -github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg= -github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= -github.com/realclientip/realclientip-go v1.0.0 h1:+yPxeC0mEaJzq1BfCt2h4BxlyrvIIBzR6suDc3BEF1U= -github.com/realclientip/realclientip-go v1.0.0/go.mod h1:CXnUdVwFRcXFJIRb/dTYqbT7ud48+Pi2pFm80bxDmcI= +github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo= +github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= -github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= +github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safchain/ethtool v0.7.0 h1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is= github.com/safchain/ethtool v0.7.0/go.mod h1:MenQKEjXdfkjD3mp2QdCk8B/hwvkrlOTm/FD4gTpFxQ= @@ -422,8 +441,9 @@ github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w= github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y= -github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= -github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -439,10 +459,13 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -464,18 +487,18 @@ github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4 github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= 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/setec v0.0.0-20260310221408-dcd97e42f251 h1:kNnJlwxSzue+VRJuDdZ/yebcMM2q9KqFmK9xQqH1YRc= -github.com/tailscale/setec v0.0.0-20260310221408-dcd97e42f251/go.mod h1:6NU8H/GLPVX2TnXAY1duyy9ylLaHwFpr0X93UPiYmNI= -github.com/tailscale/squibble v0.0.0-20260411062017-141f5d618bc4 h1:1ghkd9YIC4J7umZuu9jZz8afWJSj1hCRSzfvdI2Q3Vo= -github.com/tailscale/squibble v0.0.0-20260411062017-141f5d618bc4/go.mod h1:EVp9PDh7v69Do6aNzFfLvL0fdCph3XskGMi+WcS/uOM= -github.com/tailscale/tailsql v0.0.0-20260522170732-77aec5aabc76 h1:o7mEEIci+U0H3Ddo0JRMOxm2VGcCKaVPro/F+f3qFbg= -github.com/tailscale/tailsql v0.0.0-20260522170732-77aec5aabc76/go.mod h1:N2Dm+UzRWz01zPMDl5XHkSVq91kNPWU13uUUibcOm+c= +github.com/tailscale/setec v0.0.0-20260115174028-19d190c5556d h1:N+TtzIaGYREbLbKZB0WU0vVnMSfaqUkSf3qMEi03hwE= +github.com/tailscale/setec v0.0.0-20260115174028-19d190c5556d/go.mod h1:6NU8H/GLPVX2TnXAY1duyy9ylLaHwFpr0X93UPiYmNI= +github.com/tailscale/squibble v0.0.0-20260303070345-3ac5157f405e h1:4yfp5/YDr+TzbUME/PalYJVXAsp7zA2Gv2xQMZ9Qors= +github.com/tailscale/squibble v0.0.0-20260303070345-3ac5157f405e/go.mod h1:xJkMmR3t+thnUQhA3Q4m2VSlS5pcOq+CIjmU/xfKKx4= +github.com/tailscale/tailsql v0.0.0-20260322172246-3ab0c1744d9c h1:7lJQ/zycbk1E9e0nUiMuwIDYprFTLpWXUwiPdi+tRlI= +github.com/tailscale/tailsql v0.0.0-20260322172246-3ab0c1744d9c/go.mod h1:bpNmZdvZKmBstrZunT+NXL6hmrFw5AsuT7MGiYS8sRc= github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368 h1:0tpDdAj9sSfSZg4gMwNTdqMP592sBrq2Sm0w6ipnh7k= github.com/tailscale/web-client-prebuilt v0.0.0-20251127225136-f19339b67368/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= -github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad h1:Ky26FR5yZ5IKEB0xtm5A8xSTb06ImY7kxBFrvgOmJSg= -github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI= +github.com/tailscale/wireguard-go v0.0.0-20260304043104-4184faf59e56 h1:/R1vu+eNhg1eKstmVPEKvsJgkh4TUyb+J+Eadwv+d/I= +github.com/tailscale/wireguard-go v0.0.0-20260304043104-4184faf59e56/go.mod h1:zvaAPQrjUBWufXgqpSQ1/BYu9ZFOKnsNWLFQe+E78cM= 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/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= @@ -501,33 +524,34 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= -go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= @@ -537,39 +561,43 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/W golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= -golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= -golang.org/x/exp/typeparams v0.0.0-20260603202125-055de637280b h1:E7MAoHE/7prIY6tu29UATfH3hVHv6IWqOchjE48pTAU= -golang.org/x/exp/typeparams v0.0.0-20260603202125-055de637280b/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= -golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo= -golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -577,51 +605,59 @@ 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad h1:3iLyITS/sySRwbUKoC7ogfj2Yr1Cjs0pfaRKj5U5HEw= -google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI= +google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= @@ -638,10 +674,10 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= -modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= -modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= -modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= @@ -650,31 +686,29 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= -modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= -modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo= -modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= +modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c= +modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= -pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 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= -tailscale.com v1.101.0-pre h1:q1eBWxryj7Lz5fMvi7npSbN/fJ3q6/crvbbfMkx89F8= -tailscale.com v1.101.0-pre/go.mod h1:DQ9YBy85DpNlSyeU2XRIWzbAu3RsGp/frv+Khg57meE= -tailscale.com/client/tailscale/v2 v2.9.0 h1:zBZIIeIYXL42qvvile7d29O2DKSr3AfNc2gzd1JCf2o= -tailscale.com/client/tailscale/v2 v2.9.0/go.mod h1:FGjvGT3ThHelqo0gfdK3IN3k1dwNbRzYbQh2XO3C47U= +tailscale.com v1.97.0-pre.0.20260429005429-40088602c960 h1:I56vAGia4DV24Dbv8N07F/Awtnguvmm7PAgWCxCIdqw= +tailscale.com v1.97.0-pre.0.20260429005429-40088602c960/go.mod h1:8nwFkmNdNRtTIM2dkmr/DhbzSKeLmzusWOTacX1zVKk= zombiezen.com/go/postgrestest v1.0.1 h1:aXoADQAJmZDU3+xilYVut0pHhgc0sF8ZspPW9gFNwP4= zombiezen.com/go/postgrestest v1.0.1/go.mod h1:marlZezr+k2oSJrvXHnZUs1olHqpE9czlz8ZYkVxliQ= diff --git a/hscontrol/api/v1/api.go b/hscontrol/api/v1/api.go deleted file mode 100644 index 1ca866947..000000000 --- a/hscontrol/api/v1/api.go +++ /dev/null @@ -1,160 +0,0 @@ -// Package apiv1 is the code-first Huma implementation of the Headscale v1 API. -// Handlers are a thin adapter over hscontrol/state; Huma emits the OpenAPI 3.1 -// spec from the Go definitions (see Spec), and that spec drives the client. -// -// It depends only on the domain layer (hscontrol/state, hscontrol/types) via -// Backend, never on the hscontrol server package, so a future hscontrol/api/v2 -// can sit beside it without either importing the other. -package apiv1 - -import ( - "context" - "net/http" - "strings" - - "github.com/danielgtaylor/huma/v2" - "github.com/danielgtaylor/huma/v2/adapters/humachi" - "github.com/go-chi/chi/v5" - "github.com/juanfont/headscale/hscontrol/state" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/types/change" -) - -// Backend is the dependency surface the v1 API needs from the control plane: -// the state layer, the change-notification sink that distributes updates to -// connected nodes, and the config (only Policy.Mode and Policy.Path are read). -type Backend struct { - State *state.State - Change func(...change.Change) - Cfg *types.Config -} - -// NewAPI builds the v1 Huma API on the given chi router and registers every -// operation. Auth is enforced by a Huma middleware driven by each operation's -// declared bearer security (see authMiddleware); locally-trusted requests -// bypass it via WithLocalTrust. -func NewAPI(router chi.Router, backend Backend) huma.API { - config := huma.DefaultConfig("Headscale API", "v1") - config.Info.Description = "Headscale control server API." - - // Version the OpenAPI/docs routes under /api/v1 so a future v2 owns its own. - // These register as plain mux routes, not operations, so they never appear - // in the emitted spec or client. - config.OpenAPIPath = "/api/v1/openapi" - config.DocsPath = "/api/v1/docs" - - // The v1 API does not emit "$schema". - config.SchemasPath = "" - - // Drop the default schema-link create hook: it injects a "$schema" property - // and Link header into every response, which the v1 contract omits. - config.CreateHooks = nil - - config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{ - "bearer": { - Type: "http", - Scheme: "bearer", - }, - } - - api := humachi.New(router, config) - - // Must run before register: Huma snapshots the middleware chain at operation - // registration, so a middleware added afterwards would silently never run. - api.UseMiddleware(authMiddleware(api, backend)) - - register(api, backend) - - return api -} - -// bearerAuth is the security requirement applied to every operation: all -// /api/v1 routes require an API key. -var bearerAuth = []map[string][]string{{"bearer": {}}} - -// registrations is populated by each resource file's init(), so adding a -// resource group means adding a file rather than editing a shared point. Huma -// sorts the emitted spec, so init order does not affect output. -var registrations []func(huma.API, Backend) - -// register wires up every operation contributed by the resource files. -func register(api huma.API, b Backend) { - for _, fn := range registrations { - fn(api, b) - } -} - -// Spec emits the OpenAPI 3.1 document. The zero Backend is safe because -// handlers are registered but never invoked during emission. -func Spec() ([]byte, error) { - api := NewAPI(chi.NewMux(), Backend{}) - return api.OpenAPI().YAML() -} - -// Spec30 emits the document downgraded to OpenAPI 3.0.3, needed because the -// client generator (oapi-codegen v2) cannot yet read the 3.1 spec. -func Spec30() ([]byte, error) { - api := NewAPI(chi.NewMux(), Backend{}) - return api.OpenAPI().DowngradeYAML() -} - -// Handler builds the v1 API on a fresh mux and returns both. Callers mount the -// mux and may use mux.Match to detect which paths this API serves. -func Handler(backend Backend) (*chi.Mux, huma.API) { - mux := chi.NewMux() - api := NewAPI(mux, backend) - - return mux, api -} - -// localTrustKey marks a request as arriving over a locally-trusted transport; -// the auth middleware skips authentication for such requests. -type localTrustKey struct{} - -// WithLocalTrust wraps a handler so its requests bypass API-key authentication. -// The unix socket uses this — access to the socket is the trust boundary — as -// do in-process tests that exercise the mux directly. -func WithLocalTrust(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - next.ServeHTTP(w, req.WithContext( - context.WithValue(req.Context(), localTrustKey{}, struct{}{}), - )) - }) -} - -// authMiddleware is a pure gate enforcing the bearer API key for any operation -// that declares security; the v1 handlers do not read caller identity. -// Locally-trusted requests and operations without declared security pass -// through. b.State is nil only during spec emission, where no request is -// served, so it is never dereferenced there. -func authMiddleware(api huma.API, b Backend) func(huma.Context, func(huma.Context)) { - return func(ctx huma.Context, next func(huma.Context)) { - if ctx.Context().Value(localTrustKey{}) != nil { - next(ctx) - - return - } - - if len(ctx.Operation().Security) == 0 { - next(ctx) - - return - } - - token, ok := strings.CutPrefix(ctx.Header("Authorization"), "Bearer ") - if !ok { - _ = huma.WriteErr(api, ctx, http.StatusUnauthorized, "Unauthorized") - - return - } - - valid, err := b.State.ValidateAPIKey(token) - if err != nil || !valid { - _ = huma.WriteErr(api, ctx, http.StatusUnauthorized, "Unauthorized") - - return - } - - next(ctx) - } -} diff --git a/hscontrol/api/v1/apikeys.go b/hscontrol/api/v1/apikeys.go deleted file mode 100644 index 2411d59d9..000000000 --- a/hscontrol/api/v1/apikeys.go +++ /dev/null @@ -1,245 +0,0 @@ -package apiv1 - -import ( - "cmp" - "context" - "net/http" - "slices" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" -) - -func init() { - registrations = append(registrations, registerApiKeys) -} - -// ApiKey is the v1 ApiKey message. Timestamps are pointers so a nil source is -// emitted as JSON null, matching protojson's unset Timestamp (e.g. lastSeen on -// a fresh key). -type ApiKey struct { - ID string `format:"uint64" json:"id"` - Prefix string `json:"prefix"` - Expiration *time.Time `json:"expiration" nullable:"true"` - CreatedAt *time.Time `json:"createdAt" nullable:"true"` - LastSeen *time.Time `json:"lastSeen" nullable:"true"` -} - -// CreateApiKeyRequestBody is the v1.CreateApiKeyRequest body. -type CreateApiKeyRequestBody struct { - Expiration *time.Time `json:"expiration,omitempty"` -} - -// ExpireApiKeyRequestBody is the v1.ExpireApiKeyRequest body. -type ExpireApiKeyRequestBody struct { - Prefix string `json:"prefix,omitempty"` - ID string `format:"uint64" json:"id,omitempty"` -} - -type ( - createApiKeyInput struct { - Body CreateApiKeyRequestBody - } - createApiKeyOutput struct { - Body struct { - APIKey string `json:"apiKey"` - } - } -) - -type ( - expireApiKeyInput struct { - Body ExpireApiKeyRequestBody - } - expireApiKeyOutput struct { - Body struct{} - } -) - -type ( - listApiKeysOutput struct { - Body struct { - APIKeys []ApiKey `json:"apiKeys" nullable:"false"` - } - } -) - -type ( - deleteApiKeyInput struct { - Prefix string `path:"prefix"` - ID string `format:"uint64" query:"id"` - } - deleteApiKeyOutput struct { - Body struct{} - } -) - -func registerApiKeys(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "createApiKey", - Method: http.MethodPost, - Path: "/api/v1/apikey", - Summary: "Create API key", - Tags: []string{"ApiKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *createApiKeyInput) (*createApiKeyOutput, error) { - // CreateAPIKey requires a non-nil pointer; default a missing expiration - // to the zero time as the gRPC handler does. - var expiration time.Time - if in.Body.Expiration != nil { - expiration = *in.Body.Expiration - } - - keyStr, _, err := b.State.CreateAPIKey(&expiration) - if err != nil { - return nil, huma.Error500InternalServerError("creating api key", err) - } - - out := &createApiKeyOutput{} - out.Body.APIKey = keyStr - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "expireApiKey", - Method: http.MethodPost, - Path: "/api/v1/apikey/expire", - Summary: "Expire API key", - Tags: []string{"ApiKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *expireApiKeyInput) (*expireApiKeyOutput, error) { - key, err := lookupApiKey(b, in.Body.ID, in.Body.Prefix) - if err != nil { - return nil, err - } - - err = b.State.ExpireAPIKey(key) - if err != nil { - return nil, huma.Error500InternalServerError("expiring api key", err) - } - - return &expireApiKeyOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "listApiKeys", - Method: http.MethodGet, - Path: "/api/v1/apikey", - Summary: "List API keys", - Tags: []string{"ApiKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, _ *struct{}) (*listApiKeysOutput, error) { - keys, err := b.State.ListAPIKeys() - if err != nil { - return nil, huma.Error500InternalServerError("listing api keys", err) - } - - // Match the gRPC handler's ascending-ID ordering. - slices.SortFunc(keys, func(a, b types.APIKey) int { - return cmp.Compare(a.ID, b.ID) - }) - - out := &listApiKeysOutput{} - - out.Body.APIKeys = make([]ApiKey, len(keys)) - for i := range keys { - out.Body.APIKeys[i] = apiKeyFromState(&keys[i]) - } - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "deleteApiKey", - Method: http.MethodDelete, - Path: "/api/v1/apikey/{prefix}", - Summary: "Delete API key", - Tags: []string{"ApiKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *deleteApiKeyInput) (*deleteApiKeyOutput, error) { - key, err := lookupApiKey(b, in.ID, in.Prefix) - if err != nil { - return nil, err - } - - err = b.State.DestroyAPIKey(*key) - if err != nil { - return nil, huma.Error500InternalServerError("deleting api key", err) - } - - return &deleteApiKeyOutput{}, nil - }) -} - -// lookupApiKey resolves an API key by id or prefix; exactly one must be -// supplied. An empty or zero id counts as "no id". Unknown id/prefix maps to -// 404 via mapError. -func lookupApiKey(b Backend, idStr, prefix string) (*types.APIKey, error) { - id, err := parseApiKeyID(idStr) - if err != nil { - return nil, err - } - - hasID := id != 0 - hasPrefix := prefix != "" - - switch { - case hasID && hasPrefix: - return nil, huma.Error400BadRequest("provide either id or prefix, not both") - case hasID: - key, err := b.State.GetAPIKeyByID(id) - if err != nil { - return nil, mapError("getting api key", err) - } - - return key, nil - case hasPrefix: - key, err := b.State.GetAPIKey(prefix) - if err != nil { - return nil, mapError("getting api key", err) - } - - return key, nil - default: - return nil, huma.Error400BadRequest("must provide id or prefix") - } -} - -// parseApiKeyID decodes the optional uint64 id. Empty maps to zero; non-numeric -// is rejected with 400. -func parseApiKeyID(s string) (uint64, error) { - if s == "" { - return 0, nil - } - - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, huma.Error400BadRequest("invalid api key id", err) - } - - return id, nil -} - -// apiKeyFromState converts a domain API key into the v1 response shape, masking -// the prefix so the secret is never returned. -func apiKeyFromState(k *types.APIKey) ApiKey { - return ApiKey{ - ID: formatID(k.ID), - Prefix: apiKeyMaskedPrefix(k.Prefix), - Expiration: k.Expiration, - CreatedAt: k.CreatedAt, - LastSeen: k.LastSeen, - } -} - -// apiKeyMaskedPrefix reproduces the unexported types.APIKey.maskedPrefix. -func apiKeyMaskedPrefix(prefix string) string { - if len(prefix) == types.NewAPIKeyPrefixLength { - return "hskey-api-" + prefix + "-***" - } - - return prefix + "***" -} diff --git a/hscontrol/api/v1/auth.go b/hscontrol/api/v1/auth.go deleted file mode 100644 index e6ff980f7..000000000 --- a/hscontrol/api/v1/auth.go +++ /dev/null @@ -1,163 +0,0 @@ -package apiv1 - -import ( - "context" - "errors" - "net/http" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" -) - -func init() { - registrations = append(registrations, registerAuth) -} - -// errAuthRejected is the verdict handed to the waiting registration flow when -// an auth session is rejected. -var errAuthRejected = errors.New("auth request rejected") - -// AuthRegisterRequestBody is the v1.AuthRegisterRequest body. -type AuthRegisterRequestBody struct { - User string `json:"user,omitempty"` - AuthID string `json:"authId,omitempty"` -} - -// AuthApproveRequestBody is the v1.AuthApproveRequest body. -type AuthApproveRequestBody struct { - AuthID string `json:"authId,omitempty"` -} - -// AuthRejectRequestBody is the v1.AuthRejectRequest body. -type AuthRejectRequestBody struct { - AuthID string `json:"authId,omitempty"` -} - -type ( - authRegisterInput struct { - Body AuthRegisterRequestBody - } - authRegisterOutput struct { - Body struct { - Node Node `json:"node"` - } - } -) - -type ( - authApproveInput struct { - Body AuthApproveRequestBody - } - authApproveOutput struct { - Body struct{} - } -) - -type ( - authRejectInput struct { - Body AuthRejectRequestBody - } - authRejectOutput struct { - Body struct{} - } -) - -func registerAuth(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "authRegister", - Method: http.MethodPost, - Path: "/api/v1/auth/register", - Summary: "Register node via auth flow", - Tags: []string{"Auth"}, - Security: bearerAuth, - }, func(ctx context.Context, in *authRegisterInput) (*authRegisterOutput, error) { - // Malformed auth_id is 400; unknown user and missing pending session are - // 404 via mapError, matching the Approve/Reject handlers. - registrationID, err := types.AuthIDFromString(in.Body.AuthID) - if err != nil { - return nil, huma.Error400BadRequest("registering node", err) - } - - user, err := b.State.GetUserByName(in.Body.User) - if err != nil { - return nil, mapError("looking up user", err) - } - - node, nodeChange, err := b.State.HandleNodeFromAuthPath( - registrationID, - types.UserID(user.ID), - nil, - util.RegisterMethodCLI, - ) - if err != nil { - return nil, mapError("registering node", err) - } - - routeChange, err := b.State.AutoApproveRoutes(node) - if err != nil { - return nil, huma.Error500InternalServerError("auto approving routes", err) - } - - b.Change(nodeChange, routeChange) - - out := &authRegisterOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "authApprove", - Method: http.MethodPost, - Path: "/api/v1/auth/approve", - Summary: "Approve a pending auth session", - Tags: []string{"Auth"}, - Security: bearerAuth, - }, func(ctx context.Context, in *authApproveInput) (*authApproveOutput, error) { - authReq, err := pendingAuthRequest(b, in.Body.AuthID) - if err != nil { - return nil, err - } - - authReq.FinishAuth(types.AuthVerdict{}) - - return &authApproveOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "authReject", - Method: http.MethodPost, - Path: "/api/v1/auth/reject", - Summary: "Reject a pending auth session", - Tags: []string{"Auth"}, - Security: bearerAuth, - }, func(ctx context.Context, in *authRejectInput) (*authRejectOutput, error) { - authReq, err := pendingAuthRequest(b, in.Body.AuthID) - if err != nil { - return nil, err - } - - authReq.FinishAuth(types.AuthVerdict{ - Err: errAuthRejected, - }) - - return &authRejectOutput{}, nil - }) -} - -// pendingAuthRequest looks up the pending session for auth_id. Malformed -// auth_id is 400, unknown is 404. -func pendingAuthRequest(b Backend, rawID string) (*types.AuthRequest, error) { - authID, err := types.AuthIDFromString(rawID) - if err != nil { - return nil, huma.Error400BadRequest("invalid auth_id", err) - } - - authReq, ok := b.State.GetAuthCacheEntry(authID) - if !ok { - return nil, huma.Error404NotFound("no pending auth session for auth_id " + authID.String()) - } - - return authReq, nil -} diff --git a/hscontrol/api/v1/errors.go b/hscontrol/api/v1/errors.go deleted file mode 100644 index 478a0e450..000000000 --- a/hscontrol/api/v1/errors.go +++ /dev/null @@ -1,49 +0,0 @@ -package apiv1 - -import ( - "errors" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/state" - "gorm.io/gorm" -) - -// mapError translates a state/db-layer error into a Huma HTTP error -// (NotFound→404, invalid input→400, conflict→409, everything else→500). -// Handlers use this default mapping and may return a more specific huma.ErrorN -// directly. msg is a human context prefix, e.g. "getting node". -func mapError(msg string, err error) error { - if err == nil { - return nil - } - - switch { - case errors.Is(err, gorm.ErrRecordNotFound), - errors.Is(err, state.ErrNodeNotFound), - errors.Is(err, state.ErrNodeNotInNodeStore), - errors.Is(err, db.ErrUserNotFound), - errors.Is(err, db.ErrNodeNotFoundRegistrationCache), - errors.Is(err, state.ErrRegistrationExpired): - return huma.Error404NotFound(msg, err) - - case errors.Is(err, state.ErrGivenNameInvalid), - errors.Is(err, state.ErrGivenNameTaken), - errors.Is(err, state.ErrNodeNameNotUnique), - errors.Is(err, state.ErrNodeMarkedTaggedButHasNoTags), - errors.Is(err, state.ErrNodeHasNeitherUserNorTags), - errors.Is(err, state.ErrRequestedTagsInvalidOrNotPermitted), - errors.Is(err, db.ErrUserStillHasNodes), - errors.Is(err, db.ErrCannotChangeOIDCUser), - errors.Is(err, db.ErrPreAuthKeyNotTaggedOrOwned), - errors.Is(err, db.ErrSingleUseAuthKeyHasBeenUsed): - return huma.Error400BadRequest(msg, err) - - case errors.Is(err, state.ErrNodeKeyInUse), - errors.Is(err, state.ErrAmbiguousNodeOwnership): - return huma.Error409Conflict(msg, err) - - default: - return huma.Error500InternalServerError(msg, err) - } -} diff --git a/hscontrol/api/v1/health.go b/hscontrol/api/v1/health.go deleted file mode 100644 index 7da72e708..000000000 --- a/hscontrol/api/v1/health.go +++ /dev/null @@ -1,41 +0,0 @@ -package apiv1 - -import ( - "context" - "net/http" - - "github.com/danielgtaylor/huma/v2" -) - -// HealthResponseBody mirrors the v1 HealthResponse message. database_connectivity -// is reported true only when the database responds to a ping. -type HealthResponseBody struct { - DatabaseConnectivity bool `json:"databaseConnectivity"` -} - -type healthOutput struct { - Body HealthResponseBody -} - -func init() { - registrations = append(registrations, registerHealth) -} - -func registerHealth(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "health", - Method: http.MethodGet, - Path: "/api/v1/health", - Summary: "Health check", - Description: "Reports server health, including database connectivity.", - Tags: []string{"Health"}, - Security: bearerAuth, - }, func(ctx context.Context, _ *struct{}) (*healthOutput, error) { - err := b.State.PingDB(ctx) - if err != nil { - return nil, mapError("pinging database", err) - } - - return &healthOutput{Body: HealthResponseBody{DatabaseConnectivity: true}}, nil - }) -} diff --git a/hscontrol/api/v1/nodes.go b/hscontrol/api/v1/nodes.go deleted file mode 100644 index dbc1adfc5..000000000 --- a/hscontrol/api/v1/nodes.go +++ /dev/null @@ -1,702 +0,0 @@ -package apiv1 - -import ( - "context" - "errors" - "net/http" - "net/netip" - "slices" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "tailscale.com/net/tsaddr" - "tailscale.com/tailcfg" - "tailscale.com/types/key" -) - -func init() { - registrations = append(registrations, registerNodes) -} - -// errBackfillNotConfirmed guards BackfillNodeIPs behind explicit confirmed=true. -var errBackfillNotConfirmed = errors.New("not confirmed, aborting") - -// registerMethodToV1Enum maps the stored register method onto the -// SCREAMING_SNAKE enum string the v1 contract emits. -var registerMethodToV1Enum = map[string]string{ - util.RegisterMethodAuthKey: "REGISTER_METHOD_AUTH_KEY", - util.RegisterMethodOIDC: "REGISTER_METHOD_OIDC", - util.RegisterMethodCLI: "REGISTER_METHOD_CLI", -} - -// Node mirrors the v1 Node message. The protojson contract emits unpopulated -// fields: scalars and slices always (no omitempty), nested messages and optional -// timestamps as JSON null when unset. -type Node struct { - ID string `format:"uint64" json:"id"` - MachineKey string `json:"machineKey"` - NodeKey string `json:"nodeKey"` - DiscoKey string `json:"discoKey"` - IPAddresses []string `json:"ipAddresses" nullable:"false"` - Name string `json:"name"` - User *User `json:"user"` - LastSeen *time.Time `json:"lastSeen" nullable:"true"` - Expiry *time.Time `json:"expiry" nullable:"true"` - PreAuthKey *NodePreAuthKey `json:"preAuthKey"` - CreatedAt time.Time `json:"createdAt"` - RegisterMethod string `enum:"REGISTER_METHOD_UNSPECIFIED,REGISTER_METHOD_AUTH_KEY,REGISTER_METHOD_CLI,REGISTER_METHOD_OIDC" json:"registerMethod"` - GivenName string `json:"givenName"` - Online bool `json:"online"` - ApprovedRoutes []string `json:"approvedRoutes" nullable:"false"` - AvailableRoutes []string `json:"availableRoutes" nullable:"false"` - SubnetRoutes []string `json:"subnetRoutes" nullable:"false"` - Tags []string `json:"tags" nullable:"false"` -} - -// NodePreAuthKey is the PreAuthKey shape embedded in a Node response. The -// /preauthkey endpoints own the standalone request/response surface. -type NodePreAuthKey struct { - User *User `json:"user"` - ID string `format:"uint64" json:"id"` - Key string `json:"key"` - Reusable bool `json:"reusable"` - Ephemeral bool `json:"ephemeral"` - Used bool `json:"used"` - Expiration *time.Time `json:"expiration" nullable:"true"` - CreatedAt *time.Time `json:"createdAt" nullable:"true"` - AclTags []string `json:"aclTags" nullable:"false"` -} - -// SetTagsRequestBody mirrors v1.SetTagsRequest. -type SetTagsRequestBody struct { - Tags []string `json:"tags,omitempty"` -} - -// SetApprovedRoutesRequestBody mirrors v1.SetApprovedRoutesRequest. -type SetApprovedRoutesRequestBody struct { - Routes []string `json:"routes,omitempty"` -} - -// DebugCreateNodeRequestBody mirrors v1.DebugCreateNodeRequest. -type DebugCreateNodeRequestBody struct { - User string `json:"user,omitempty"` - Key string `json:"key,omitempty"` - Name string `json:"name,omitempty"` - Routes []string `json:"routes,omitempty"` -} - -type ( - getNodeInput struct { - NodeID string `format:"uint64" path:"nodeId"` - } - nodeOutput struct { - Body struct { - Node Node `json:"node"` - } - } -) - -type ( - listNodesInput struct { - User string `query:"user"` - } - listNodesOutput struct { - Body struct { - Nodes []Node `json:"nodes" nullable:"false"` - } - } -) - -type ( - deleteNodeInput struct { - NodeID string `format:"uint64" path:"nodeId"` - } - deleteNodeOutput struct { - Body struct{} - } -) - -// ExpireNodeRequestBody mirrors v1.ExpireNodeRequest. Both fields are optional; -// an absent or all-zero body expires the node immediately, as gRPC does. -type ExpireNodeRequestBody struct { - Expiry *time.Time `json:"expiry,omitempty"` - DisableExpiry bool `json:"disableExpiry,omitempty"` -} - -type expireNodeInput struct { - NodeID string `format:"uint64" path:"nodeId"` - Body *ExpireNodeRequestBody `required:"false"` -} - -type renameNodeInput struct { - NodeID string `format:"uint64" path:"nodeId"` - NewName string `path:"newName"` -} - -type setTagsInput struct { - NodeID string `format:"uint64" path:"nodeId"` - Body SetTagsRequestBody -} - -type setApprovedRoutesInput struct { - NodeID string `format:"uint64" path:"nodeId"` - Body SetApprovedRoutesRequestBody -} - -type registerNodeInput struct { - User string `query:"user"` - Key string `query:"key"` -} - -type backfillNodeIPsInput struct { - Confirmed bool `query:"confirmed"` -} - -type backfillNodeIPsOutput struct { - Body struct { - Changes []string `json:"changes" nullable:"false"` - } -} - -type debugCreateNodeInput struct { - Body DebugCreateNodeRequestBody -} - -func registerNodes(api huma.API, b Backend) { - registerNodeReadOps(api, b) - registerNodeWriteOps(api, b) - registerNodeAdminOps(api, b) -} - -func registerNodeReadOps(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "getNode", - Method: http.MethodGet, - Path: "/api/v1/node/{nodeId}", - Summary: "Get node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *getNodeInput) (*nodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - node, ok := b.State.GetNodeByID(nodeID) - if !ok { - return nil, huma.Error404NotFound("node not found") - } - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "listNodes", - Method: http.MethodGet, - Path: "/api/v1/node", - Summary: "List nodes", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *listNodesInput) (*listNodesOutput, error) { - nodes := b.State.ListNodes() - if in.User != "" { - user, err := b.State.GetUserByName(in.User) - if err != nil { - return nil, mapError("listing nodes", err) - } - - nodes = b.State.ListNodesByUser(types.UserID(user.ID)) - } - - out := &listNodesOutput{} - out.Body.Nodes = make([]Node, nodes.Len()) - - for i, node := range nodes.All() { - n := nodeFromView(node) - - // Tags-as-identity: tagged nodes are presented as the special - // TaggedDevices user. - if node.IsTagged() { - user := userFromView(types.TaggedDevices.View()) - n.User = &user - } - - // SubnetRoutes is the routes actively served, exit routes included. - n.SubnetRoutes = util.PrefixesToString( - append(b.State.GetNodePrimaryRoutes(node.ID()), node.ExitRoutes()...), - ) - - out.Body.Nodes[i] = n - } - - // Match the gRPC handler's ascending-ID ordering. - slices.SortFunc(out.Body.Nodes, func(a, b Node) int { - return cmpNodeID(a.ID, b.ID) - }) - - return out, nil - }) -} - -func registerNodeWriteOps(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "deleteNode", - Method: http.MethodDelete, - Path: "/api/v1/node/{nodeId}", - Summary: "Delete node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *deleteNodeInput) (*deleteNodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - node, ok := b.State.GetNodeByID(nodeID) - if !ok { - return nil, huma.Error404NotFound("node not found") - } - - nodeChange, err := b.State.DeleteNode(node) - if err != nil { - return nil, huma.Error500InternalServerError("deleting node", err) - } - - b.Change(nodeChange) - - return &deleteNodeOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "expireNode", - Method: http.MethodPost, - Path: "/api/v1/node/{nodeId}/expire", - Summary: "Expire node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *expireNodeInput) (*nodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - // gRPC parity: disableExpiry => nil expiry (never expires); explicit - // expiry honoured; absent/zero body expires now. Both set is a 400. - var ( - disableExpiry bool - customExpiry *time.Time - ) - - if in.Body != nil { - disableExpiry = in.Body.DisableExpiry - customExpiry = in.Body.Expiry - } - - if disableExpiry && customExpiry != nil { - return nil, huma.Error400BadRequest("cannot set both disable_expiry and expiry") - } - - expiry := time.Now() - - switch { - case disableExpiry: - node, nodeChange, expErr := b.State.SetNodeExpiry(nodeID, nil) - if expErr != nil { - return nil, mapError("expiring node", expErr) - } - - b.Change(nodeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - case customExpiry != nil: - expiry = *customExpiry - } - - node, nodeChange, err := b.State.SetNodeExpiry(nodeID, &expiry) - if err != nil { - return nil, mapError("expiring node", err) - } - - b.Change(nodeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "renameNode", - Method: http.MethodPost, - Path: "/api/v1/node/{nodeId}/rename/{newName}", - Summary: "Rename node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *renameNodeInput) (*nodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - node, nodeChange, err := b.State.RenameNode(nodeID, in.NewName) - if err != nil { - return nil, mapError("renaming node", err) - } - - b.Change(nodeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "setTags", - Method: http.MethodPost, - Path: "/api/v1/node/{nodeId}/tags", - Summary: "Set tags", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *setTagsInput) (*nodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - // Tagged nodes must keep at least one tag, so reject an empty set - // before touching state, as gRPC does. - if len(in.Body.Tags) == 0 { - return nil, huma.Error400BadRequest( - "cannot remove all tags from a node - tagged nodes must have at least one tag", - ) - } - - for _, tag := range in.Body.Tags { - tagErr := validateTag(tag) - if tagErr != nil { - return nil, huma.Error400BadRequest("setting tags", tagErr) - } - } - - _, found := b.State.GetNodeByID(nodeID) - if !found { - return nil, huma.Error404NotFound("node not found") - } - - node, nodeChange, err := b.State.SetNodeTags(nodeID, in.Body.Tags) - if err != nil { - return nil, huma.Error400BadRequest("setting tags", err) - } - - b.Change(nodeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) -} - -func registerNodeAdminOps(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "setApprovedRoutes", - Method: http.MethodPost, - Path: "/api/v1/node/{nodeId}/approve_routes", - Summary: "Set approved routes", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *setApprovedRoutesInput) (*nodeOutput, error) { - nodeID, err := parseNodeID(in.NodeID) - if err != nil { - return nil, err - } - - var newApproved []netip.Prefix - - for _, route := range in.Body.Routes { - prefix, parseErr := netip.ParsePrefix(route) - if parseErr != nil { - return nil, huma.Error400BadRequest("parsing route", parseErr) - } - - // One exit route implies both families, else the client won't - // annotate the node as an exit node. - if prefix == tsaddr.AllIPv4() || prefix == tsaddr.AllIPv6() { - newApproved = append(newApproved, tsaddr.AllIPv4(), tsaddr.AllIPv6()) - } else { - newApproved = append(newApproved, prefix) - } - } - - slices.SortFunc(newApproved, netip.Prefix.Compare) - newApproved = slices.Compact(newApproved) - - node, nodeChange, err := b.State.SetApprovedRoutes(nodeID, newApproved) - if err != nil { - return nil, mapError("setting approved routes", err) - } - - b.Change(nodeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - // SubnetRoutes here excludes exit routes, unlike the list handler. - out.Body.Node.SubnetRoutes = util.PrefixesToString( - b.State.GetNodePrimaryRoutes(node.ID()), - ) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "registerNode", - Method: http.MethodPost, - Path: "/api/v1/node/register", - Summary: "Register node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *registerNodeInput) (*nodeOutput, error) { - registrationID, err := types.AuthIDFromString(in.Key) - if err != nil { - return nil, huma.Error400BadRequest("registering node", err) - } - - user, err := b.State.GetUserByName(in.User) - if err != nil { - return nil, mapError("looking up user", err) - } - - node, nodeChange, err := b.State.HandleNodeFromAuthPath( - registrationID, - types.UserID(user.ID), - nil, - util.RegisterMethodCLI, - ) - if err != nil { - return nil, mapError("registering node", err) - } - - routeChange, err := b.State.AutoApproveRoutes(node) - if err != nil { - return nil, huma.Error500InternalServerError("auto approving routes", err) - } - - // Empty changes are ignored by the change sink. - b.Change(nodeChange, routeChange) - - out := &nodeOutput{} - out.Body.Node = nodeFromView(node) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "backfillNodeIPs", - Method: http.MethodPost, - Path: "/api/v1/node/backfillips", - Summary: "Backfill node IPs", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *backfillNodeIPsInput) (*backfillNodeIPsOutput, error) { - if !in.Confirmed { - return nil, huma.Error400BadRequest("backfilling node IPs", errBackfillNotConfirmed) - } - - changes, err := b.State.BackfillNodeIPs() - if err != nil { - return nil, huma.Error500InternalServerError("backfilling node IPs", err) - } - - out := &backfillNodeIPsOutput{} - out.Body.Changes = changes - - if out.Body.Changes == nil { - out.Body.Changes = []string{} - } - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "debugCreateNode", - Method: http.MethodPost, - Path: "/api/v1/debug/node", - Summary: "Debug create node", - Tags: []string{"Nodes"}, - Security: bearerAuth, - }, func(ctx context.Context, in *debugCreateNodeInput) (*nodeOutput, error) { - user, err := b.State.GetUserByName(in.Body.User) - if err != nil { - return nil, mapError("looking up user", err) - } - - routes, err := util.StringToIPPrefix(in.Body.Routes) - if err != nil { - return nil, huma.Error400BadRequest("parsing routes", err) - } - - registrationID, err := types.AuthIDFromString(in.Body.Key) - if err != nil { - return nil, huma.Error400BadRequest("debug creating node", err) - } - - regData := &types.RegistrationData{ - NodeKey: key.NewNode().Public(), - MachineKey: key.NewMachine().Public(), - Hostname: in.Body.Name, - Expiry: &time.Time{}, // zero time, not nil, to keep proto JSON round-trip semantics - } - - authRegReq := types.NewRegisterAuthRequest(regData) - b.State.SetAuthCacheEntry(registrationID, authRegReq) - - // Synthetic echo; the real node is created later via the auth path - // from the cached registration data. - echoNode := types.Node{ - NodeKey: regData.NodeKey, - MachineKey: regData.MachineKey, - Hostname: regData.Hostname, - User: user, - Expiry: &time.Time{}, - LastSeen: &time.Time{}, - Hostinfo: &tailcfg.Hostinfo{ - Hostname: in.Body.Name, - OS: "TestOS", - RoutableIPs: routes, - }, - } - - out := &nodeOutput{} - out.Body.Node = nodeFromView(echoNode.View()) - - return out, nil - }) -} - -// nodeFromView builds the Node response from a NodeView, reading through the -// view accessors. SubnetRoutes is left empty; callers that serve routes set it -// explicitly. -func nodeFromView(view types.NodeView) Node { - n := Node{ - ID: view.StringID(), - MachineKey: view.MachineKey().String(), - NodeKey: view.NodeKey().String(), - DiscoKey: view.DiscoKey().String(), - IPAddresses: nonNilStrings(view.IPsAsString()), - Name: view.Hostname(), - CreatedAt: view.CreatedAt(), - RegisterMethod: registerMethodEnum(view.RegisterMethod()), - GivenName: view.GivenName(), - Online: view.IsOnline().Valid() && view.IsOnline().Get(), - ApprovedRoutes: nonNilStrings(util.PrefixesToString(view.ApprovedRoutes().AsSlice())), - AvailableRoutes: nonNilStrings(util.PrefixesToString(view.AnnouncedRoutes())), - SubnetRoutes: []string{}, - Tags: nonNilStrings(view.Tags().AsSlice()), - } - - if view.User().Valid() { - user := userFromView(view.User()) - n.User = &user - } - - if view.AuthKey().Valid() { - n.PreAuthKey = nodePreAuthKeyFromView(view.AuthKey()) - } - - if view.LastSeen().Valid() { - ls := view.LastSeen().Get() - n.LastSeen = &ls - } - - if view.Expiry().Valid() { - exp := view.Expiry().Get() - n.Expiry = &exp - } - - return n -} - -// nodePreAuthKeyFromView builds the embedded NodePreAuthKey, masking the key to -// its prefix (legacy plaintext keys are shown in full). -func nodePreAuthKeyFromView(key types.PreAuthKeyView) *NodePreAuthKey { - pak := &NodePreAuthKey{ - ID: formatID(key.ID()), - Key: maskedPreAuthKey(key), - Reusable: key.Reusable(), - Ephemeral: key.Ephemeral(), - Used: key.Used(), - AclTags: nonNilStrings(key.Tags().AsSlice()), - } - - if key.User().Valid() { - user := userFromView(key.User()) - pak.User = &user - } - - if key.Expiration().Valid() { - exp := key.Expiration().Get() - pak.Expiration = &exp - } - - if key.CreatedAt().Valid() { - created := key.CreatedAt().Get() - pak.CreatedAt = &created - } - - return pak -} - -// registerMethodEnum maps the stored register method onto the v1 enum string, -// defaulting to REGISTER_METHOD_UNSPECIFIED for unknown values. -func registerMethodEnum(method string) string { - if enum, ok := registerMethodToV1Enum[method]; ok { - return enum - } - - return "REGISTER_METHOD_UNSPECIFIED" -} - -func nonNilStrings(s []string) []string { - if s == nil { - return []string{} - } - - return s -} - -// cmpNodeID orders two decimal node-ID strings numerically, matching the gRPC -// handler's ascending-ID ordering. -func cmpNodeID(a, b string) int { - ai, _ := strconv.ParseUint(a, 10, 64) - bi, _ := strconv.ParseUint(b, 10, 64) - - switch { - case ai < bi: - return -1 - case ai > bi: - return 1 - default: - return 0 - } -} - -func parseNodeID(s string) (types.NodeID, error) { - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, huma.Error400BadRequest( - "type mismatch, parameter: node_id, error: " + err.Error(), - ) - } - - return types.NodeID(id), nil -} diff --git a/hscontrol/api/v1/policy.go b/hscontrol/api/v1/policy.go deleted file mode 100644 index 452148113..000000000 --- a/hscontrol/api/v1/policy.go +++ /dev/null @@ -1,191 +0,0 @@ -package apiv1 - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "time" - - "github.com/danielgtaylor/huma/v2" - policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" -) - -func init() { - registrations = append(registrations, registerPolicy) -} - -// PolicyRequestBody carries the HuJSON policy document as a string, for both -// v1.SetPolicyRequest and v1.CheckPolicyRequest. -type PolicyRequestBody struct { - Policy string `json:"policy,omitempty"` -} - -// PolicyResponseBody is the v1.GetPolicyResponse/SetPolicyResponse body. Fields -// carry no omitempty so zero values are emitted (EmitUnpopulated parity). -type PolicyResponseBody struct { - Policy string `json:"policy"` - UpdatedAt time.Time `json:"updatedAt"` -} - -type ( - getPolicyInput struct{} - getPolicyOutput struct { - Body PolicyResponseBody - } - - setPolicyInput struct { - Body PolicyRequestBody - } - setPolicyOutput struct { - Body PolicyResponseBody - } - - checkPolicyInput struct { - Body PolicyRequestBody - } - checkPolicyOutput struct { - Body struct{} - } -) - -func registerPolicy(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "getPolicy", - Method: http.MethodGet, - Path: "/api/v1/policy", - Summary: "Get policy", - Tags: []string{"Policy"}, - Security: bearerAuth, - }, func(ctx context.Context, _ *getPolicyInput) (*getPolicyOutput, error) { - switch b.Cfg.Policy.Mode { - case types.PolicyModeDB: - p, err := b.State.GetPolicy() - if err != nil { - return nil, huma.Error500InternalServerError("loading ACL from database", err) - } - - out := &getPolicyOutput{} - out.Body.Policy = p.Data - out.Body.UpdatedAt = p.UpdatedAt - - return out, nil - case types.PolicyModeFile: - absPath := util.AbsolutePathFromConfigPath(b.Cfg.Policy.Path) - - f, err := os.Open(absPath) - if err != nil { - return nil, huma.Error500InternalServerError( - fmt.Sprintf("reading policy from path %q", absPath), err, - ) - } - defer f.Close() - - data, err := io.ReadAll(f) - if err != nil { - return nil, huma.Error500InternalServerError("reading policy from file", err) - } - - out := &getPolicyOutput{} - out.Body.Policy = string(data) - - return out, nil - } - - return nil, huma.Error500InternalServerError(fmt.Sprintf( - "no supported policy mode found in configuration, policy.mode: %q", - b.Cfg.Policy.Mode, - ), nil) - }) - - huma.Register(api, huma.Operation{ - OperationID: "setPolicy", - Method: http.MethodPut, - Path: "/api/v1/policy", - Summary: "Set policy", - Tags: []string{"Policy"}, - Security: bearerAuth, - }, func(ctx context.Context, in *setPolicyInput) (*setPolicyOutput, error) { - if b.Cfg.Policy.Mode != types.PolicyModeDB { - // Policy updates are only valid in DB mode; otherwise 400. - return nil, huma.Error400BadRequest( - types.ErrPolicyUpdateIsDisabled.Error(), types.ErrPolicyUpdateIsDisabled, - ) - } - - p := in.Body.Policy - - // Reject policy that would fail when building a map response. SSH rule - // validation needs a node, so a server with no nodes can't catch every - // case here. - nodes := b.State.ListNodes() - - _, err := b.State.SetPolicy([]byte(p)) - if err != nil { - return nil, huma.Error400BadRequest("setting policy", err) - } - - if nodes.Len() > 0 { - _, err = b.State.SSHPolicy(nodes.At(0)) - if err != nil { - return nil, huma.Error400BadRequest("verifying SSH rules", err) - } - } - - updated, err := b.State.SetPolicyInDB(p) - if err != nil { - return nil, huma.Error500InternalServerError("setting policy", err) - } - - // Reload even when content is unchanged: routes manually disabled before - // may now qualify for auto-approval, so they must be re-evaluated. - cs, err := b.State.ReloadPolicy() - if err != nil { - return nil, huma.Error500InternalServerError("reloading policy", err) - } - - if len(cs) > 0 { - b.Change(cs...) - } - - out := &setPolicyOutput{} - out.Body.Policy = updated.Data - out.Body.UpdatedAt = updated.UpdatedAt - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "checkPolicy", - Method: http.MethodPost, - Path: "/api/v1/policy/check", - Summary: "Check policy", - Description: "Validates the given policy against the server's live users and nodes without persisting it.", - Tags: []string{"Policy"}, - Security: bearerAuth, - }, func(ctx context.Context, in *checkPolicyInput) (*checkPolicyOutput, error) { - polB := []byte(in.Body.Policy) - - users, err := b.State.ListAllUsers() - if err != nil { - return nil, huma.Error500InternalServerError("loading users", err) - } - - nodes := b.State.ListNodes() - - pm, err := policyv2.NewPolicyManager(polB, users, nodes) - if err != nil { - return nil, huma.Error400BadRequest(err.Error(), err) - } - - _, err = pm.SetPolicy(polB) - if err != nil { - return nil, huma.Error400BadRequest(err.Error(), err) - } - - return &checkPolicyOutput{}, nil - }) -} diff --git a/hscontrol/api/v1/preauthkeys.go b/hscontrol/api/v1/preauthkeys.go deleted file mode 100644 index 9afb0aa22..000000000 --- a/hscontrol/api/v1/preauthkeys.go +++ /dev/null @@ -1,318 +0,0 @@ -package apiv1 - -import ( - "cmp" - "context" - "net/http" - "slices" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" -) - -func init() { - registrations = append(registrations, registerPreAuthKeys) -} - -// PreAuthKey is the v1 PreAuthKey message. User is a pointer with no omitempty -// so tagged (system-created) keys emit "user":null. Expiration and CreatedAt -// are always emitted, zero-stamped when unset. -type PreAuthKey struct { - User *User `json:"user"` - ID string `format:"uint64" json:"id"` - Key string `json:"key"` - Reusable bool `json:"reusable"` - Ephemeral bool `json:"ephemeral"` - Used bool `json:"used"` - Expiration time.Time `json:"expiration"` - CreatedAt time.Time `json:"createdAt"` - ACLTags []string `json:"aclTags" nullable:"false"` -} - -// CreatePreAuthKeyRequestBody is the v1.CreatePreAuthKeyRequest body. Every -// field is optional, hence omitempty throughout. -type CreatePreAuthKeyRequestBody struct { - User string `format:"uint64" json:"user,omitempty"` - Reusable bool `json:"reusable,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` - Expiration *time.Time `json:"expiration,omitempty"` - ACLTags []string `json:"aclTags,omitempty"` -} - -// ExpirePreAuthKeyRequestBody is the v1.ExpirePreAuthKeyRequest body. -type ExpirePreAuthKeyRequestBody struct { - ID string `format:"uint64" json:"id,omitempty"` -} - -type ( - createPreAuthKeyInput struct { - Body CreatePreAuthKeyRequestBody - } - preAuthKeyOutput struct { - Body struct { - PreAuthKey PreAuthKey `json:"preAuthKey"` - } - } -) - -type ( - expirePreAuthKeyInput struct { - Body ExpirePreAuthKeyRequestBody - } - expirePreAuthKeyOutput struct { - Body struct{} - } -) - -type ( - deletePreAuthKeyInput struct { - ID string `format:"uint64" query:"id"` - } - deletePreAuthKeyOutput struct { - Body struct{} - } -) - -type listPreAuthKeysOutput struct { - Body struct { - PreAuthKeys []PreAuthKey `json:"preAuthKeys" nullable:"false"` - } -} - -func registerPreAuthKeys(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "createPreAuthKey", - Method: http.MethodPost, - Path: "/api/v1/preauthkey", - Summary: "Create pre-auth key", - Tags: []string{"PreAuthKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *createPreAuthKeyInput) (*preAuthKeyOutput, error) { - user, err := parsePreAuthKeyUser(in.Body.User) - if err != nil { - return nil, err - } - - for _, tag := range in.Body.ACLTags { - tagErr := validateTag(tag) - if tagErr != nil { - return nil, huma.Error400BadRequest("invalid tag", tagErr) - } - } - - // CreatePreAuthKey requires a non-nil pointer; zero-stamp when unset. - var expiration time.Time - if in.Body.Expiration != nil { - expiration = *in.Body.Expiration - } - - var userID *types.UserID - - if user != 0 { - u, getErr := b.State.GetUserByID(user) - if getErr != nil { - return nil, mapError("creating pre-auth key", getErr) - } - - userID = u.TypedID() - } - - preAuthKey, err := b.State.CreatePreAuthKey( - userID, - in.Body.Reusable, - in.Body.Ephemeral, - &expiration, - in.Body.ACLTags, - ) - if err != nil { - // A key that is neither tagged nor user-owned is invalid input (400). - return nil, mapError("creating pre-auth key", err) - } - - out := &preAuthKeyOutput{} - out.Body.PreAuthKey = preAuthKeyNewToResponse(preAuthKey) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "expirePreAuthKey", - Method: http.MethodPost, - Path: "/api/v1/preauthkey/expire", - Summary: "Expire pre-auth key", - Tags: []string{"PreAuthKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *expirePreAuthKeyInput) (*expirePreAuthKeyOutput, error) { - id, err := parsePreAuthKeyID(in.Body.ID) - if err != nil { - return nil, err - } - - err = b.State.ExpirePreAuthKey(id) - if err != nil { - // An unknown key id maps to 404. - return nil, mapError("expiring pre-auth key", err) - } - - return &expirePreAuthKeyOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "deletePreAuthKey", - Method: http.MethodDelete, - Path: "/api/v1/preauthkey", - Summary: "Delete pre-auth key", - Tags: []string{"PreAuthKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, in *deletePreAuthKeyInput) (*deletePreAuthKeyOutput, error) { - // DELETE has no body: id is bound from the query string. - id, err := parsePreAuthKeyID(in.ID) - if err != nil { - return nil, err - } - - err = b.State.DeletePreAuthKey(id) - if err != nil { - // An unknown key id maps to 404. - return nil, mapError("deleting pre-auth key", err) - } - - return &deletePreAuthKeyOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "listPreAuthKeys", - Method: http.MethodGet, - Path: "/api/v1/preauthkey", - Summary: "List pre-auth keys", - Tags: []string{"PreAuthKeys"}, - Security: bearerAuth, - }, func(ctx context.Context, _ *struct{}) (*listPreAuthKeysOutput, error) { - preAuthKeys, err := b.State.ListPreAuthKeys() - if err != nil { - return nil, huma.Error500InternalServerError("listing pre-auth keys", err) - } - - // Match the gRPC handler's ascending-ID ordering. - slices.SortFunc(preAuthKeys, func(a, b types.PreAuthKey) int { - return cmp.Compare(a.ID, b.ID) - }) - - out := &listPreAuthKeysOutput{} - - out.Body.PreAuthKeys = make([]PreAuthKey, len(preAuthKeys)) - for i := range preAuthKeys { - out.Body.PreAuthKeys[i] = preAuthKeyToResponse(&preAuthKeys[i]) - } - - return out, nil - }) -} - -// preAuthKeyNewToResponse builds the v1 response for a freshly created key. The -// plaintext key is returned only here; Used is always false. -func preAuthKeyNewToResponse(key *types.PreAuthKeyNew) PreAuthKey { - out := PreAuthKey{ - ID: formatID(key.ID), - Key: key.Key, - Reusable: key.Reusable, - Ephemeral: key.Ephemeral, - ACLTags: nonNilTags(key.Tags), - } - - if key.User != nil { - u := userFromView(key.User.View()) - out.User = &u - } - - if key.Expiration != nil { - out.Expiration = *key.Expiration - } - - if key.CreatedAt != nil { - out.CreatedAt = *key.CreatedAt - } - - return out -} - -// preAuthKeyToResponse builds the v1 response for a stored key, with its key -// field masked (see maskedPreAuthKey). -func preAuthKeyToResponse(key *types.PreAuthKey) PreAuthKey { - out := PreAuthKey{ - ID: formatID(key.ID), - Key: maskedPreAuthKey(key.View()), - Reusable: key.Reusable, - Ephemeral: key.Ephemeral, - Used: key.Used, - ACLTags: nonNilTags(key.Tags), - } - - if key.User != nil { - u := userFromView(key.User.View()) - out.User = &u - } - - if key.Expiration != nil { - out.Expiration = *key.Expiration - } - - if key.CreatedAt != nil { - out.CreatedAt = *key.CreatedAt - } - - return out -} - -// maskedPreAuthKey masks new keys (those with a stored prefix) so the secret is -// never returned; legacy plaintext keys are returned in full for backwards -// compatibility. -func maskedPreAuthKey(key types.PreAuthKeyView) string { - if key.Prefix() != "" { - return "hskey-auth-" + key.Prefix() + "-***" - } - - return key.Key() -} - -// nonNilTags ensures aclTags serializes as [] rather than null, matching -// EmitUnpopulated output. -func nonNilTags(tags []string) []string { - if tags == nil { - return []string{} - } - - return tags -} - -// parsePreAuthKeyUser parses the optional uint64 user field. Empty means "no -// user" (user 0); non-numeric is rejected with 400. -func parsePreAuthKeyUser(s string) (types.UserID, error) { - if s == "" { - return 0, nil - } - - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, huma.Error400BadRequest("invalid user id", err) - } - - return types.UserID(id), nil -} - -// parsePreAuthKeyID parses the uint64 key id. Empty means id 0; non-numeric is -// rejected with 400. -func parsePreAuthKeyID(s string) (uint64, error) { - if s == "" { - return 0, nil - } - - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, huma.Error400BadRequest("invalid pre-auth key id", err) - } - - return id, nil -} diff --git a/hscontrol/api/v1/tags.go b/hscontrol/api/v1/tags.go deleted file mode 100644 index bd3b4b94c..000000000 --- a/hscontrol/api/v1/tags.go +++ /dev/null @@ -1,29 +0,0 @@ -package apiv1 - -import ( - "errors" - "strings" -) - -// ACL tag validation, shared by the node and pre-auth-key resources. These -// reproduce the gRPC validateTag checks and messages. -var ( - errTagMissingPrefix = errors.New("tag must start with the string 'tag:'") - errTagNotLowercase = errors.New("tag should be lowercase") - errTagHasSpaces = errors.New("tags must not contain spaces") -) - -// validateTag reports whether an ACL tag is well formed: it must start with -// "tag:", be lowercase, and contain no spaces. -func validateTag(tag string) error { - switch { - case !strings.HasPrefix(tag, "tag:"): - return errTagMissingPrefix - case strings.ToLower(tag) != tag: - return errTagNotLowercase - case len(strings.Fields(tag)) > 1: - return errTagHasSpaces - default: - return nil - } -} diff --git a/hscontrol/api/v1/types.go b/hscontrol/api/v1/types.go deleted file mode 100644 index e8ffbd879..000000000 --- a/hscontrol/api/v1/types.go +++ /dev/null @@ -1,52 +0,0 @@ -package apiv1 - -import ( - "strconv" - "time" - - "github.com/juanfont/headscale/hscontrol/types" -) - -// The v1 contract follows protojson: 64-bit integers are JSON strings (avoiding -// precision loss above 2^53), timestamps are RFC 3339, and zero values are -// emitted. Hence response fields carry NO omitempty; request types keep -// omitempty so their fields stay optional in the spec. - -// formatID renders a uint64 identifier as the contract's decimal string. -func formatID[T ~uint64 | ~uint](id T) string { - return strconv.FormatUint(uint64(id), 10) -} - -// User mirrors the v1 User message. -type User struct { - ID string `format:"uint64" json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - DisplayName string `json:"displayName"` - Email string `json:"email"` - ProviderID string `json:"providerId"` - Provider string `json:"provider"` - ProfilePicURL string `json:"profilePicUrl"` -} - -// userFromView converts a domain user into the v1 response shape, reading -// through the [types.UserView] accessors: Name falls back to Username() -// (email/provider/id) when the stored Name is empty, so OIDC users display -// their email. -func userFromView(u types.UserView) User { - name := u.Name() - if name == "" { - name = u.Username() - } - - return User{ - ID: formatID(u.ID()), - Name: name, - CreatedAt: u.CreatedAt(), - DisplayName: u.DisplayName(), - Email: u.Email(), - ProviderID: u.ProviderIdentifier().String, - Provider: u.Provider(), - ProfilePicURL: u.ProfilePicURL(), - } -} diff --git a/hscontrol/api/v1/users.go b/hscontrol/api/v1/users.go deleted file mode 100644 index 9468d67a1..000000000 --- a/hscontrol/api/v1/users.go +++ /dev/null @@ -1,233 +0,0 @@ -package apiv1 - -import ( - "cmp" - "context" - "net/http" - "slices" - "strconv" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" - "gorm.io/gorm" -) - -func init() { - registrations = append(registrations, registerUsers) -} - -// CreateUserRequestBody mirrors v1.CreateUserRequest. -type CreateUserRequestBody struct { - Name string `json:"name,omitempty"` - DisplayName string `json:"displayName,omitempty"` - Email string `json:"email,omitempty"` - PictureURL string `json:"pictureUrl,omitempty"` -} - -type ( - createUserInput struct { - Body CreateUserRequestBody - } - userOutput struct { - Body struct { - User User `json:"user"` - } - } -) - -type ( - renameUserInput struct { - OldID string `format:"uint64" path:"oldId"` - NewName string `path:"newName"` - } - - deleteUserInput struct { - ID string `format:"uint64" path:"id"` - } - deleteUserOutput struct { - Body struct{} - } -) - -type ( - listUsersInput struct { - ID string `format:"uint64" query:"id"` - Name string `query:"name"` - Email string `query:"email"` - } - listUsersOutput struct { - Body struct { - Users []User `json:"users" nullable:"false"` - } - } -) - -func registerUsers(api huma.API, b Backend) { - huma.Register(api, huma.Operation{ - OperationID: "createUser", - Method: http.MethodPost, - Path: "/api/v1/user", - Summary: "Create user", - Tags: []string{"Users"}, - Security: bearerAuth, - }, func(ctx context.Context, in *createUserInput) (*userOutput, error) { - // Pre-check yields a 409 for the common case; the DB unique constraint - // is the real guard. - if in.Body.Name != "" { - _, err := b.State.GetUserByName(in.Body.Name) - if err == nil { - return nil, huma.Error409Conflict("user already exists") - } - } - - user, policyChanged, err := b.State.CreateUser(types.User{ - Name: in.Body.Name, - DisplayName: in.Body.DisplayName, - Email: in.Body.Email, - ProfilePicURL: in.Body.PictureURL, - }) - if err != nil { - return nil, mapError("creating user", err) - } - - b.Change(policyChanged) - - out := &userOutput{} - out.Body.User = userFromView(user.View()) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "renameUser", - Method: http.MethodPost, - Path: "/api/v1/user/{oldId}/rename/{newName}", - Summary: "Rename user", - Tags: []string{"Users"}, - Security: bearerAuth, - }, func(ctx context.Context, in *renameUserInput) (*userOutput, error) { - oldID, err := parseUserID(in.OldID) - if err != nil { - return nil, err - } - - oldUser, err := b.State.GetUserByID(oldID) - if err != nil { - return nil, mapError("renaming user", err) - } - - _, c, err := b.State.RenameUser(types.UserID(oldUser.ID), in.NewName) - if err != nil { - return nil, mapError("renaming user", err) - } - - b.Change(c) - - newUser, err := b.State.GetUserByName(in.NewName) - if err != nil { - return nil, huma.Error500InternalServerError("renaming user", err) - } - - out := &userOutput{} - out.Body.User = userFromView(newUser.View()) - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "deleteUser", - Method: http.MethodDelete, - Path: "/api/v1/user/{id}", - Summary: "Delete user", - Tags: []string{"Users"}, - Security: bearerAuth, - }, func(ctx context.Context, in *deleteUserInput) (*deleteUserOutput, error) { - id, err := parseUserID(in.ID) - if err != nil { - return nil, err - } - - user, err := b.State.GetUserByID(id) - if err != nil { - return nil, mapError("deleting user", err) - } - - policyChanged, err := b.State.DeleteUser(types.UserID(user.ID)) - if err != nil { - return nil, mapError("deleting user", err) - } - - b.Change(policyChanged) - - return &deleteUserOutput{}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "listUsers", - Method: http.MethodGet, - Path: "/api/v1/user", - Summary: "List users", - Tags: []string{"Users"}, - Security: bearerAuth, - }, func(ctx context.Context, in *listUsersInput) (*listUsersOutput, error) { - // Gateway parity: a non-numeric id is a 400 even when other filters win. - if in.ID != "" { - _, err := strconv.ParseUint(in.ID, 10, 64) - if err != nil { - return nil, huma.Error400BadRequest("invalid id", err) - } - } - - users, err := listUsersFiltered(b, in) - if err != nil { - return nil, huma.Error500InternalServerError("listing users", err) - } - - // Match the gRPC handler's ascending-ID ordering. - slices.SortFunc(users, func(a, b types.User) int { - return cmp.Compare(a.ID, b.ID) - }) - - out := &listUsersOutput{} - - out.Body.Users = make([]User, len(users)) - for i := range users { - out.Body.Users[i] = userFromView(users[i].View()) - } - - return out, nil - }) -} - -// listUsersFiltered reproduces the gRPC ListUsers precedence: name, then email, -// then id, otherwise all users. -func listUsersFiltered(b Backend, in *listUsersInput) ([]types.User, error) { - switch { - case in.Name != "": - return b.State.ListUsersWithFilter(&types.User{Name: in.Name}) - case in.Email != "": - return b.State.ListUsersWithFilter(&types.User{Email: in.Email}) - case in.ID != "": - id, err := strconv.ParseUint(in.ID, 10, 64) - if err != nil { - return nil, err - } - - if id == 0 { - return b.State.ListAllUsers() - } - - return b.State.ListUsersWithFilter(&types.User{Model: gorm.Model{ID: uint(id)}}) - default: - return b.State.ListAllUsers() - } -} - -func parseUserID(s string) (types.UserID, error) { - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, huma.Error400BadRequest("invalid user id", err) - } - - return types.UserID(id), nil -} diff --git a/hscontrol/api/v2/README.md b/hscontrol/api/v2/README.md deleted file mode 100644 index 90fad3638..000000000 --- a/hscontrol/api/v2/README.md +++ /dev/null @@ -1,124 +0,0 @@ -# API v2: Headscale's v2 API - -This is Headscale's v2 HTTP API, served at `/api/v2`. Some of its endpoints are -**ported from Tailscale's API**, reusing Tailscale's wire shapes, so the -Tailscale ecosystem that cannot talk to Headscale today works: the -[Terraform/OpenTofu provider], [tscli], and the official [Go client] -(`tailscale.com/client/tailscale/v2`). - -It is **not** a port of the whole Tailscale API. Ported endpoints are added one -at a time, only as we need them; a headscale-native v2 endpoint may use -headscale's own conventions. The headscale-native admin API stays at `/api/v1` -(`hscontrol/api/v1`). This guide is for the endpoints **ported from Tailscale**. - -[Terraform/OpenTofu provider]: https://registry.terraform.io/providers/tailscale/tailscale/latest -[tscli]: https://github.com/jaxxstorm/tscli -[Go client]: https://pkg.go.dev/tailscale.com/client/tailscale/v2 - -## Conventions - -- Operations derived from Tailscale carry the `Tailscale compat` tag. -- The `{tailnet}` path segment must be `-` (the single Headscale tailnet); - anything else is `404`. See `requireDefaultTailnet`. -- Errors use **Tailscale's** body (`{"message","data","status"}`), installed as - a per-API transform (`tailscaleErrorTransformer` in `errors.go`). A future - headscale-native v2 operation would keep Huma's RFC 9457 problem+json. -- Auth accepts a credential as **HTTP Basic** (key as username, what the SDK - sends) or **Bearer**: an admin API key (`hskey-api-…`), or an OAuth access - token (`hskey-oauthtok-…`). See `authMiddleware`. -- Each operation declares the Tailscale scope it requires (`auth_keys`, - `oauth_keys`, `devices:core`, `devices:routes`, `policy_file`, - `feature_settings`, each with a `:read` subset, plus `all`/`all:read`). - `requireScope` records it both for the middleware and in the generated - OpenAPI, as an `x-required-scope` extension and a sentence in the operation - description, so the scope shows up in the docs and spec. Enforcement: an - **admin API key is all-access** (scope checks skipped); an **OAuth access - token is scope-limited**, the middleware checks the operation's declared scope - against the token's grant (`scope.Grants`, where a write scope subsumes its - `:read` and `all`/`all:read` are super-scopes). The two are told apart by - credential prefix. -- Resolve one entity by id with a typed getter (`GetNodeByID`, `GetUserByID`, - `GetAPIKeyByID`, `GetPreAuthKeyByID`); add one to state/db if it is missing - rather than scanning a `List`. Build responses from the view accessors - (`NodeView`/`UserView`/`PreAuthKeyView`), never `AsStruct()`. -- Reuse upstream wire shapes, but declare the request/response structs here: - Huma reflects these to build the OpenAPI schema, and the upstream `Key`'s - `ExpirySeconds *time.Duration` marshals as nanoseconds, which the spec and - every client read as seconds. - -## OAuth clients & scopes - -Most of the Tailscale ecosystem (the Terraform provider, `tscli`, the Go client) -accepts **either** an API key **or OAuth 2.0 client-credentials**; the Kubernetes -operator is OAuth-only. Supporting OAuth lets all of them drive Headscale. - -- **OAuth clients** are not a separate resource; they are `keyType:"client"` on - the keys endpoint, exactly as Tailscale does it. Create - (`POST /api/v2/tailnet/-/keys` with `{"keyType":"client","scopes":[…],"tags":[…]}`) - returns a `Key` whose `id` is the client id and whose `key` is the secret, - **shown once**; get/list never re-expose it. The secret is - `hskey-client--`, embedding the client id so the token - endpoint derives it from the secret (Tailscale's `get-authkey` trick). See - `keys.go` (`createOAuthClient`) and `db/oauth.go`. -- **Token endpoint** `POST /api/v2/oauth/token` (`oauth.go`) is a plain handler, - not a Huma operation: it takes `application/x-www-form-urlencoded` and emits - RFC 6749 OAuth2 error bodies (`{"error","error_description"}`). Credentials - arrive in the body or HTTP Basic; optional space-delimited `scope`/`tags` - narrow the token to a subset of the client's grant. It returns a 1-hour - `Bearer` access token (`hskey-oauthtok-…`). -- **Scope enforcement** is the one seam in `authMiddleware`. **Tag enforcement**: - an auth key minted by a token may only carry tags the token holds, or tags - owned-by them via the policy `tagOwners` (`State.TagOwnedByTags` → - `policy/v2`), so e.g. an operator token tagged `tag:k8s-operator` may mint - `tag:k8s` keys. -- Credentials/tokens are stored like API keys: a public id/prefix plus an - **Argon2id** hash of the secret (no JWT, no signing keys). `OAuthClient` and - `OAuthAccessToken` live in `types/oauth.go` and `db/oauth.go`. - -## Adding an endpoint - -Worked example: the keys resource (`keys.go`) = Tailscale auth keys = Headscale -pre-auth keys. - -1. **Read the Tailscale spec.** Find the operation in the [Tailscale API - reference](https://tailscale.com/api) (OpenAPI 3.1). Note method, path, - request/response schema, and which variant(s) Headscale supports (auth keys - only, for keys). - -2. **Capture golden samples.** Pull the request + response JSON examples from the - spec, prune to the variant, and use them as the assertion in the contract - test. _Acceptance: the captured request and response are recorded in the - test._ - -3. **Map to Headscale.** Write the field ↔ field ↔ `state` call mapping. Record - gaps and the decision for each (e.g. Tailscale `preauthorized` has no - Headscale equivalent: accepted, ignored, echoed back). _Acceptance: every - request field is consumed or deliberately ignored; every response field has a - source._ - -4. **Implement the Huma operation.** Declare named request/response structs with - validation/`default`/`example`/`doc` tags; tag the operation `Tailscale -compat`; declare its `Errors`; enforce the tailnet and scope. Map state - errors with `mapError`. _Acceptance: `go build ./hscontrol/api/v2/` and the - operation appears in `Spec()`._ - -5. **Contract test (in-process, `humatest`).** Assert the server accepts the - golden request and returns the golden response shape, with secrets and - timestamps neutralised. Pin the wire facts (e.g. `expirySeconds` in seconds, - the list `{"keys":[...]}` envelope, the error `message`). See - `hscontrol/apiv2_keys_test.go`. _Acceptance: the test is green._ - -6. **Roundtrip the real clients.** Add a `t.Run` subtest to `TestAPIv2` - (`hscontrol/servertest/apiv2_test.go`) for each of the Go client, tscli, and - OpenTofu, full create→read→list→delete against one shared server on a real - loopback port (`servertest.WithRealListener`). tscli and tofu come from the - nix dev shell; a missing binary fails the test. _Acceptance: `nix develop -c -go test ./hscontrol/servertest/ -run TestAPIv2` is green._ - -7. **Update the CLI** only if the v2 operation fully replaces a v1 one. Tailscale - has no separate key-expire verb (its `DELETE` _is_ the revoke), so v2 maps - `DELETE` to a soft revoke: the key stays retrievable with `invalid: true` - until the collector reaps it (`preauth_keys.revoked_retention`), the - equivalent of v1 `preauthkeys expire`. `headscale preauthkeys` still stays on - v1 for now (it is the cross-user admin surface), but the verb gap that - previously blocked migration is closed. diff --git a/hscontrol/api/v2/acl.go b/hscontrol/api/v2/acl.go deleted file mode 100644 index ff6917933..000000000 --- a/hscontrol/api/v2/acl.go +++ /dev/null @@ -1,229 +0,0 @@ -package apiv2 - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "errors" - "net/http" - "os" - "strings" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" -) - -func init() { - registrations = append(registrations, registerACL) -} - -const ( - contentTypeJSON = "application/json" - contentTypeHuJSON = "application/hujson" -) - -// defaultPolicy is served by GET when no policy is set, and is what an If-Match -// of "ts-default" matches against. Allow-all, matching the cluster's behaviour -// with no configured policy. -const defaultPolicy = `{ - // Headscale default policy. Allows all communication. - "acls": [ - {"action": "accept", "src": ["*"], "dst": ["*:*"]}, - ], -} -` - -type getACLInput struct { - Tailnet string `path:"tailnet"` - Accept string `header:"Accept"` - Details bool `doc:"Accepted for compatibility; ignored." query:"details"` -} - -type setACLInput struct { - Tailnet string `path:"tailnet"` - IfMatch string `header:"If-Match"` - Accept string `header:"Accept"` - // RawBody captures the raw HuJSON or JSON policy bytes; huma feeds them here - // regardless of Content-Type. The declared type only shapes the OpenAPI schema. - RawBody []byte `contentType:"application/json"` -} - -func registerACL(api huma.API, b Backend) { - aclTags := []string{"Policy", "Tailscale compat"} - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "getACL", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/acl", - Summary: "Get the policy file", - Tags: aclTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusInternalServerError}, - }, scope.PolicyFileRead), func(ctx context.Context, in *getACLInput) (*huma.StreamResponse, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - data, err := currentPolicy(b) - if err != nil { - return nil, err - } - - return streamPolicy(data, aclContentType(in.Accept)), nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "setACL", - Method: http.MethodPost, - Path: "/api/v2/tailnet/{tailnet}/acl", - Summary: "Set the policy file", - Tags: aclTags, - Security: security, - DefaultStatus: http.StatusOK, - // The body is an opaque HuJSON document captured raw; skip huma's - // schema validation (which would expect a base64 string). - SkipValidateBody: true, - Errors: []int{ - http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, - http.StatusNotFound, http.StatusPreconditionFailed, http.StatusInternalServerError, - }, - }, scope.PolicyFile), func(ctx context.Context, in *setACLInput) (*huma.StreamResponse, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - if b.Cfg.Policy.Mode != types.PolicyModeDB { - return nil, huma.Error400BadRequest( - types.ErrPolicyUpdateIsDisabled.Error(), types.ErrPolicyUpdateIsDisabled, - ) - } - - if in.IfMatch != "" { - current, err := currentPolicy(b) - if err != nil { - return nil, err - } - - if !etagMatches(in.IfMatch, current) { - return nil, huma.Error412PreconditionFailed("precondition failed, invalid old hash") - } - } - - // Mirror the v1 setPolicy flow: validate, SSH-check, persist, reload. - nodes := b.State.ListNodes() - - _, err = b.State.SetPolicy(in.RawBody) - if err != nil { - return nil, huma.Error400BadRequest("setting policy", err) - } - - if nodes.Len() > 0 { - _, err = b.State.SSHPolicy(nodes.At(0)) - if err != nil { - return nil, huma.Error400BadRequest("verifying SSH rules", err) - } - } - - updated, err := b.State.SetPolicyInDB(string(in.RawBody)) - if err != nil { - return nil, huma.Error500InternalServerError("setting policy", err) - } - - cs, err := b.State.ReloadPolicy() - if err != nil { - return nil, huma.Error500InternalServerError("reloading policy", err) - } - - if len(cs) > 0 { - b.Change(cs...) - } - - return streamPolicy([]byte(updated.Data), aclContentType(in.Accept)), nil - }) -} - -// currentPolicy returns the stored policy bytes, or the allow-all default when -// none is set. File mode reads the configured path; DB mode reads the database. -func currentPolicy(b Backend) ([]byte, error) { - switch b.Cfg.Policy.Mode { - case types.PolicyModeDB: - p, err := b.State.GetPolicy() - if err != nil { - if errors.Is(err, types.ErrPolicyNotFound) { - return []byte(defaultPolicy), nil - } - - return nil, huma.Error500InternalServerError("loading policy", err) - } - - if p.Data == "" { - return []byte(defaultPolicy), nil - } - - return []byte(p.Data), nil - - case types.PolicyModeFile: - path := util.AbsolutePathFromConfigPath(b.Cfg.Policy.Path) - if path == "" { - return []byte(defaultPolicy), nil - } - - data, err := os.ReadFile(path) - if err != nil { - return nil, huma.Error500InternalServerError("reading policy file", err) - } - - return data, nil - } - - return nil, huma.Error500InternalServerError("unsupported policy mode", nil) -} - -// streamPolicy writes the policy bytes as-is with the chosen content type and -// a content-addressed ETag, bypassing huma's JSON marshaler so HuJSON survives. -func streamPolicy(data []byte, contentType string) *huma.StreamResponse { - return &huma.StreamResponse{Body: func(ctx huma.Context) { - ctx.SetHeader("Content-Type", contentType) - ctx.SetHeader("ETag", policyETag(data)) - ctx.SetStatus(http.StatusOK) - _, _ = ctx.BodyWriter().Write(data) - }} -} - -// aclContentType serves HuJSON only when explicitly asked; everything else -// (including an empty Accept) gets application/json. The bytes are identical. -func aclContentType(accept string) string { - if strings.Contains(accept, contentTypeHuJSON) { - return contentTypeHuJSON - } - - return contentTypeJSON -} - -// policyETag is the quoted hex SHA-256 of the policy bytes: stable across reads, -// changes iff the policy changes. -func policyETag(data []byte) string { - sum := sha256.Sum256(data) - - return `"` + hex.EncodeToString(sum[:]) + `"` -} - -// etagMatches reports whether an If-Match header satisfies the current policy. -// "*" always matches; "ts-default" matches only when no policy is set (an -// approximation of Tailscale's untouched-default semantics). -func etagMatches(ifMatch string, current []byte) bool { - ifMatch = strings.TrimSpace(ifMatch) - - switch ifMatch { - case "*": - return true - case `"ts-default"`, "ts-default": - return string(current) == defaultPolicy - default: - return ifMatch == policyETag(current) - } -} diff --git a/hscontrol/api/v2/api.go b/hscontrol/api/v2/api.go deleted file mode 100644 index e42c51b83..000000000 --- a/hscontrol/api/v2/api.go +++ /dev/null @@ -1,325 +0,0 @@ -// Package apiv2 is Headscale's v2 HTTP API, served at /api/v2. -// -// Where the v1 API (hscontrol/api/v1) is the headscale-native admin surface, v2 -// additionally ports selected endpoints from Tailscale's API, reusing -// Tailscale's wire shapes (paths, request/response JSON, error body), so the -// existing Tailscale ecosystem (the Terraform/OpenTofu provider, tscli, and -// tailscale.com/client/tailscale/v2) can drive Headscale unchanged. Ported -// operations carry the "Tailscale compat" tag; a headscale-native v2 operation -// may use headscale's own conventions instead. See README.md for the porting -// guide. -// -// It depends only on the domain layer (hscontrol/state, hscontrol/types, and -// the db error sentinels), never on the hscontrol server package, so it sits -// beside the v1 API without either importing the other. -package apiv2 - -import ( - "context" - "encoding/base64" - "maps" - "net/http" - "strings" - - "github.com/danielgtaylor/huma/v2" - "github.com/danielgtaylor/huma/v2/adapters/humachi" - "github.com/go-chi/chi/v5" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/state" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/types/change" -) - -// Backend is the dependency surface the v2 API needs from the control plane: -// the state layer, the change-notification sink that distributes node/policy -// updates to connected clients, and the config (Policy.Mode/Path, Node.Expiry, -// and TLS are read by the ACL and settings handlers). -type Backend struct { - State *state.State - Change func(...change.Change) - Cfg *types.Config -} - -// security is the requirement applied to authenticated operations: an API key -// presented as HTTP Basic (the key as username, what the Tailscale SDK sends) -// or as a Bearer token. -var security = []map[string][]string{{"basicAuth": {}}, {"bearerAuth": {}}} - -// registrations is populated by each resource file's init(), so adding a -// resource means adding a file rather than editing a shared list. -var registrations []func(huma.API, Backend) - -// Register wires up every operation contributed by the resource files. Exported -// so tests can register the v2 operations onto a humatest API. -func Register(api huma.API, b Backend) { - for _, fn := range registrations { - fn(api, b) - } -} - -// Config returns the Huma configuration shared by the production API and tests: -// the v2 OpenAPI/docs paths, the basic+bearer security schemes, and the -// Tailscale error transform. Suppressing SchemasPath/CreateHooks keeps "$schema" -// out of the emitted bodies, matching the Tailscale wire contract. -func Config() huma.Config { - config := huma.DefaultConfig("Headscale API", "v2") - config.Info.Description = "Headscale v2 API. Some endpoints are ported from / compatible with the Tailscale API (tagged \"Tailscale compat\")." - - config.OpenAPIPath = "/api/v2/openapi" - config.DocsPath = "/api/v2/docs" - config.SchemasPath = "" - config.CreateHooks = nil - - config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{ - "basicAuth": {Type: "http", Scheme: "basic"}, - "bearerAuth": {Type: "http", Scheme: "bearer"}, - } - - config.Transformers = append(config.Transformers, tailscaleErrorTransformer) - - // Accept application/hujson request bodies (the Tailscale SDK sends the - // policy file that way). The bytes are captured raw by the ACL handler, so - // reusing the JSON format is only to satisfy huma's content-type check. - // Clone first: config.Formats aliases huma's shared DefaultFormats map. - formats := maps.Clone(config.Formats) - formats["application/hujson"] = formats["application/json"] - config.Formats = formats - - return config -} - -// NewAPI builds the v2 Huma API on router, installs the auth+scope middleware, -// and registers every operation. -func NewAPI(router chi.Router, backend Backend) huma.API { - api := humachi.New(router, Config()) - - // Must run before Register: Huma snapshots the middleware chain at operation - // registration, so a middleware added afterwards would silently never run. - api.UseMiddleware(authMiddleware(api, backend)) - - Register(api, backend) - - // The OAuth token endpoint is a plain route, not a Huma operation (see - // oauth.go); register it on the same router. - registerOAuthToken(router, backend) - - return api -} - -// Handler builds the v2 API on a fresh mux and returns both, for mounting. -func Handler(backend Backend) (*chi.Mux, huma.API) { - mux := chi.NewMux() - api := NewAPI(mux, backend) - - return mux, api -} - -// Spec emits the OpenAPI 3.1 document. The zero Backend is safe because -// handlers are registered but never invoked during emission. -func Spec() ([]byte, error) { - api := NewAPI(chi.NewMux(), Backend{}) - - return api.OpenAPI().YAML() -} - -// Spec30 emits the document downgraded to OpenAPI 3.0.3, needed because -// oapi-codegen cannot yet read 3.1; the typed client is generated from this. -func Spec30() ([]byte, error) { - api := NewAPI(chi.NewMux(), Backend{}) - - return api.OpenAPI().DowngradeYAML() -} - -type contextKey int - -const ( - localTrustKey contextKey = iota - ownerUserKey - principalScopesKey - principalTagsKey -) - -// WithLocalTrust marks a request as arriving over a locally-trusted transport -// (the unix socket), bypassing API-key authentication. Reserved for a future -// v2 socket mount; the network listener always authenticates. -func WithLocalTrust(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - next.ServeHTTP(w, req.WithContext( - context.WithValue(req.Context(), localTrustKey, struct{}{}), - )) - }) -} - -// authMiddleware authenticates the API key (HTTP Basic with the key as the -// username, what the Tailscale SDK sends, or Bearer), records the key's owning -// user for handlers, and enforces the operation's required scope. -func authMiddleware(api huma.API, b Backend) func(huma.Context, func(huma.Context)) { - return func(ctx huma.Context, next func(huma.Context)) { - if ctx.Context().Value(localTrustKey) != nil { - next(ctx) - - return - } - - if len(ctx.Operation().Security) == 0 { - next(ctx) - - return - } - - token, ok := bearerOrBasicToken(ctx.Header("Authorization")) - if !ok { - _ = huma.WriteErr(api, ctx, http.StatusUnauthorized, "unauthorized") - - return - } - - // An OAuth access token is scope-limited; an admin API key is all-access. - // They are told apart by prefix so a scoped token can never be mistaken - // for an all-access key. - if strings.HasPrefix(token, types.AccessTokenPrefix) { - at, err := b.State.AuthenticateAccessToken(token) - if err != nil { - _ = huma.WriteErr(api, ctx, http.StatusUnauthorized, "unauthorized") - - return - } - - if want, ok := requiredScope(ctx.Operation()); ok && !scope.Grants(scope.Parse(at.Scopes), want) { - _ = huma.WriteErr(api, ctx, http.StatusForbidden, - "token is missing the required scope "+string(want)) - - return - } - - // The keys handler multiplexes on keyType, so its required scope and - // permitted tags depend on the body; carry the token's scopes and tags - // for it to finish the check the static middleware cannot. - ctx = huma.WithValue(ctx, principalScopesKey, at.Scopes) - ctx = huma.WithValue(ctx, principalTagsKey, at.Tags) - - next(ctx) - - return - } - - key, err := b.State.AuthenticateAPIKey(token) - if err != nil { - _ = huma.WriteErr(api, ctx, http.StatusUnauthorized, "unauthorized") - - return - } - - // An admin API key is all-access: its operations are not scope-checked. - // Record its owning user (may be unset) so handlers can create user-owned - // keys on its behalf. - if key.UserID != nil { - ctx = huma.WithValue(ctx, ownerUserKey, types.UserID(*key.UserID)) - } - - next(ctx) - } -} - -// bearerOrBasicToken extracts the API key from an Authorization header. The -// Tailscale SDK sends the key as the Basic-auth username with an empty -// password; curl and humans may use Bearer. -func bearerOrBasicToken(header string) (string, bool) { - if token, ok := strings.CutPrefix(header, "Bearer "); ok { - return token, token != "" - } - - if encoded, ok := strings.CutPrefix(header, "Basic "); ok { - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return "", false - } - - username, _, _ := strings.Cut(string(raw), ":") - - return username, username != "" - } - - return "", false -} - -// ownerUser returns the user the request's API key belongs to, if any. -func ownerUser(ctx context.Context) (types.UserID, bool) { - uid, ok := ctx.Value(ownerUserKey).(types.UserID) - - return uid, ok -} - -// principalScopes returns the scopes granted to the request's OAuth access -// token, and whether the request authenticated with one. ok is false for an -// admin API key, which is all-access and not scope-checked. -func principalScopes(ctx context.Context) ([]string, bool) { - scopes, ok := ctx.Value(principalScopesKey).([]string) - - return scopes, ok -} - -// principalTags returns the tags granted to the request's OAuth access token, -// and whether the request authenticated with one. An admin API key is not an -// OAuth token, so ok is false and its key creation is unrestricted by tags. -func principalTags(ctx context.Context) ([]string, bool) { - tags, ok := ctx.Value(principalTagsKey).([]string) - - return tags, ok -} - -// requireDefaultTailnet rejects any tailnet other than "-". Headscale is -// single-tailnet; the Tailscale SDK sends "-" (its default tailnet). A non-"-" -// value is "no such tailnet", a 404, which lets the SDK's IsNotFound behave. -func requireDefaultTailnet(tailnet string) error { - if tailnet != "-" { - return huma.Error404NotFound("tailnet not found") - } - - return nil -} - -// The scope vocabulary and the grant predicate live in the hscontrol/scope -// package; this file only wires a required scope onto each huma operation and -// reads it back in the middleware. - -// scopeMetaKey keys the per-operation required scope in huma.Operation.Metadata. -const scopeMetaKey = "headscale.scope" - -// requireScope records op's required scope, both in its Metadata (where the auth -// middleware reads it back) and in the generated OpenAPI document: an -// x-required-scope extension for machine consumers and a Description line so the -// rendered docs state what each operation needs. -func requireScope(op huma.Operation, s scope.Scope) huma.Operation { - if op.Metadata == nil { - op.Metadata = map[string]any{} - } - - op.Metadata[scopeMetaKey] = s - - if op.Extensions == nil { - op.Extensions = map[string]any{} - } - - op.Extensions["x-required-scope"] = string(s) - - note := "Requires the `" + string(s) + "` OAuth scope (an admin API key is all-access)." - if op.Description == "" { - op.Description = note - } else { - op.Description += "\n\n" + note - } - - return op -} - -// requiredScope returns the scope an operation declared via requireScope, if any. -func requiredScope(op *huma.Operation) (scope.Scope, bool) { - if op == nil || op.Metadata == nil { - return "", false - } - - s, ok := op.Metadata[scopeMetaKey].(scope.Scope) - - return s, ok -} diff --git a/hscontrol/api/v2/devices.go b/hscontrol/api/v2/devices.go deleted file mode 100644 index 5bdb553f8..000000000 --- a/hscontrol/api/v2/devices.go +++ /dev/null @@ -1,486 +0,0 @@ -package apiv2 - -import ( - "context" - "net/http" - "net/netip" - "slices" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "tailscale.com/net/tsaddr" -) - -func init() { - registrations = append(registrations, registerDevices) -} - -// Device is the Tailscale device response. Headscale nodes map onto it; fields -// Headscale does not track are emitted as their zero value. The route slices -// are only populated for the fields=all variant, matching Tailscale. -type Device struct { - Addresses []string `json:"addresses" nullable:"false"` - Name string `json:"name"` - ID string `json:"id"` - NodeID string `json:"nodeId"` - Authorized bool `json:"authorized"` - User string `json:"user"` - Tags []string `json:"tags" nullable:"false"` - KeyExpiryDisabled bool `json:"keyExpiryDisabled"` - Created time.Time `json:"created"` - Expires *time.Time `json:"expires,omitempty"` - Hostname string `json:"hostname"` - IsEphemeral bool `json:"isEphemeral"` - LastSeen *time.Time `json:"lastSeen,omitempty"` - MachineKey string `json:"machineKey"` - NodeKey string `json:"nodeKey"` - OS string `json:"os"` - ClientVersion string `json:"clientVersion"` - UpdateAvailable bool `json:"updateAvailable"` - - // Populated only when fields=all is requested. - AdvertisedRoutes []string `json:"advertisedRoutes,omitempty"` - EnabledRoutes []string `json:"enabledRoutes,omitempty"` -} - -// DeviceRoutes is the GET /device/{id}/routes response: what the node announces -// vs which of those are approved (Tailscale calls approved routes "enabled"). -type DeviceRoutes struct { - Advertised []string `json:"advertisedRoutes" nullable:"false"` - Enabled []string `json:"enabledRoutes" nullable:"false"` -} - -// Request bodies match the Tailscale SDK wire shapes. -type ( - setAuthorizedRequest struct { - Authorized bool `json:"authorized"` - } - setNameRequest struct { - Name string `json:"name"` - } - // setTagsRequest.Tags is intentionally not nullable:"false": the SDK sends - // "tags":null for "make untagged", which the handler accepts as a no-op - // rather than failing to decode (Headscale cannot untag a node). - setTagsRequest struct { - Tags []string `json:"tags"` - } - setKeyRequest struct { - KeyExpiryDisabled bool `json:"keyExpiryDisabled"` - } - setSubnetRoutesRequest struct { - Routes []string `json:"routes" nullable:"false"` - } -) - -type ( - deviceByIDInput struct { - DeviceID string `doc:"Device id (the decimal node id)." path:"id"` - Fields string `doc:"Set to \"all\" for route fields." query:"fields"` - } - listDevicesInput struct { - Tailnet string `doc:"Tailnet; must be \"-\"." path:"tailnet"` - Fields string `query:"fields"` - } - setAuthorizedInput struct { - DeviceID string `path:"id"` - Body setAuthorizedRequest - } - setNameInput struct { - DeviceID string `path:"id"` - Body setNameRequest - } - setTagsInput struct { - DeviceID string `path:"id"` - Body setTagsRequest - } - setKeyInput struct { - DeviceID string `path:"id"` - Body setKeyRequest - } - setSubnetRoutesInput struct { - DeviceID string `path:"id"` - Body setSubnetRoutesRequest - } - - deviceOutput struct{ Body Device } - deviceRoutesOutput struct{ Body DeviceRoutes } - listDevicesOutput struct { - Body struct { - Devices []Device `json:"devices" nullable:"false"` - } - } - emptyOutput struct{ Body struct{} } -) - -func registerDevices(api huma.API, b Backend) { - deviceTags := []string{"Devices", "Tailscale compat"} - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "getDevice", - Method: http.MethodGet, - Path: "/api/v2/device/{id}", - Summary: "Get a device", - Tags: deviceTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCoreRead), func(ctx context.Context, in *deviceByIDInput) (*deviceOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - return &deviceOutput{Body: deviceFromView(node, in.Fields == "all")}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "listDevices", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/devices", - Summary: "List devices", - Tags: deviceTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCoreRead), func(ctx context.Context, in *listDevicesInput) (*listDevicesOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - nodes := b.State.ListNodes() - allFields := in.Fields == "all" - - out := &listDevicesOutput{} - out.Body.Devices = make([]Device, 0, nodes.Len()) - - for _, node := range nodes.All() { - out.Body.Devices = append(out.Body.Devices, deviceFromView(node, allFields)) - } - - return out, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "deleteDevice", - Method: http.MethodDelete, - Path: "/api/v2/device/{id}", - Summary: "Delete a device", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCore), func(ctx context.Context, in *deviceByIDInput) (*emptyOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - nodeChange, err := b.State.DeleteNode(node) - if err != nil { - return nil, mapError("deleting device", err) - } - - b.Change(nodeChange) - - return &emptyOutput{}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "authorizeDevice", - Method: http.MethodPost, - Path: "/api/v2/device/{id}/authorized", - Summary: "Authorize a device", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCore), func(ctx context.Context, in *setAuthorizedInput) (*emptyOutput, error) { - _, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - // Headscale nodes are authorized the moment they register; there is no - // de-authorize state. Accept authorized=true as a no-op; reject false so - // callers are not misled into thinking the device is fenced off. - if !in.Body.Authorized { - return nil, huma.Error400BadRequest( - "Headscale does not support de-authorizing a device; delete or expire it instead", - ) - } - - return &emptyOutput{}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "setDeviceName", - Method: http.MethodPost, - Path: "/api/v2/device/{id}/name", - Summary: "Set a device's name", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCore), func(ctx context.Context, in *setNameInput) (*emptyOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - _, nodeChange, err := b.State.RenameNode(node.ID(), in.Body.Name) - if err != nil { - return nil, mapError("renaming device", err) - } - - b.Change(nodeChange) - - return &emptyOutput{}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "setDeviceTags", - Method: http.MethodPost, - Path: "/api/v2/device/{id}/tags", - Summary: "Set a device's tags", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCore), func(ctx context.Context, in *setTagsInput) (*emptyOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - // Headscale cannot make a node untagged (tags-as-identity is one-way), so - // an empty/null tag set is accepted as a no-op rather than rejected. This - // keeps tooling lifecycles such as Terraform destroy working; the SDK - // sends "tags":null for "make untagged". - if len(in.Body.Tags) == 0 { - return &emptyOutput{}, nil - } - - // An OAuth token may only assign tags within its grant (held directly or - // owned by a held tag per policy); an admin API key is unrestricted. The - // devices:core scope alone must not let a token stamp an arbitrary policy - // tag (e.g. tag:prod) onto any node. SetNodeTags still enforces that each - // tag exists in policy. - if tokenTags, isOAuth := principalTags(ctx); isOAuth { - for _, tag := range in.Body.Tags { - if !b.State.TagOwnedByTags(tag, tokenTags) { - return nil, huma.Error403Forbidden("token may not assign tag " + tag) - } - } - } - - _, nodeChange, err := b.State.SetNodeTags(node.ID(), in.Body.Tags) - if err != nil { - return nil, mapError("setting device tags", err) - } - - b.Change(nodeChange) - - return &emptyOutput{}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "setDeviceKey", - Method: http.MethodPost, - Path: "/api/v2/device/{id}/key", - Summary: "Set a device's key settings", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesCore), func(ctx context.Context, in *setKeyInput) (*emptyOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - // Only disabling expiry maps cleanly (expiry=nil never expires). - // Re-enabling has no target expiry in the Tailscale request and Headscale - // stores no original, so it is accepted as a no-op (keeps Terraform - // destroy working) rather than guessing a lifetime. - if !in.Body.KeyExpiryDisabled { - return &emptyOutput{}, nil - } - - _, nodeChange, err := b.State.SetNodeExpiry(node.ID(), nil) - if err != nil { - return nil, mapError("setting device key expiry", err) - } - - b.Change(nodeChange) - - return &emptyOutput{}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "setDeviceRoutes", - Method: http.MethodPost, - Path: "/api/v2/device/{id}/routes", - Summary: "Set a device's enabled subnet routes", - Tags: deviceTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesRoutes), func(ctx context.Context, in *setSubnetRoutesInput) (*deviceRoutesOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - approved, err := parseRoutes(in.Body.Routes) - if err != nil { - return nil, err - } - - updated, nodeChange, err := b.State.SetApprovedRoutes(node.ID(), approved) - if err != nil { - return nil, mapError("setting device routes", err) - } - - b.Change(nodeChange) - - return &deviceRoutesOutput{Body: routesFromView(updated)}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "getDeviceRoutes", - Method: http.MethodGet, - Path: "/api/v2/device/{id}/routes", - Summary: "Get a device's subnet routes", - Tags: deviceTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.DevicesRoutesRead), func(ctx context.Context, in *deviceByIDInput) (*deviceRoutesOutput, error) { - node, err := lookupNode(b, in.DeviceID) - if err != nil { - return nil, err - } - - return &deviceRoutesOutput{Body: routesFromView(node)}, nil - }) -} - -// lookupNode resolves a device id to its NodeView, mapping a malformed or -// unknown id to 404 (the Tailscale SDK keys IsNotFound off the status code). -func lookupNode(b Backend, rawID string) (types.NodeView, error) { - nodeID, err := parseNodeID(rawID) - if err != nil { - return types.NodeView{}, err - } - - node, ok := b.State.GetNodeByID(nodeID) - if !ok { - return types.NodeView{}, huma.Error404NotFound("device not found") - } - - return node, nil -} - -// parseRoutes parses the enabled-route strings, expanding an exit route to both -// families (else the node is not annotated as an exit node), then sorts/dedups. -func parseRoutes(routes []string) ([]netip.Prefix, error) { - var approved []netip.Prefix - - for _, route := range routes { - prefix, err := netip.ParsePrefix(route) - if err != nil { - return nil, huma.Error400BadRequest("parsing route", err) - } - - if prefix == tsaddr.AllIPv4() || prefix == tsaddr.AllIPv6() { - approved = append(approved, tsaddr.AllIPv4(), tsaddr.AllIPv6()) - } else { - approved = append(approved, prefix) - } - } - - slices.SortFunc(approved, netip.Prefix.Compare) - - return slices.Compact(approved), nil -} - -// deviceFromView maps a Headscale node onto the Tailscale Device, reading -// through the [types.NodeView] accessors. allFields gates the route slices, -// which Tailscale only returns for fields=all. -func deviceFromView(view types.NodeView, allFields bool) Device { - id := view.StringID() - - d := Device{ - Addresses: emptyIfNil(view.IPsAsString()), - Name: view.GivenName(), - ID: id, - NodeID: id, - Authorized: true, // Headscale has no post-registration de-auth state. - User: deviceUser(view), - Tags: emptyIfNil(view.Tags().AsSlice()), - KeyExpiryDisabled: !view.Expiry().Valid(), - Created: view.CreatedAt(), - Hostname: view.Hostname(), - IsEphemeral: view.IsEphemeral(), - MachineKey: view.MachineKey().String(), - NodeKey: view.NodeKey().String(), - OS: hostinfoOS(view), - ClientVersion: hostinfoVersion(view), - } - - if view.Expiry().Valid() { - exp := view.Expiry().Get() - d.Expires = &exp - } - - // LastSeen is reported only when the device is offline, matching tailcfg. - if view.LastSeen().Valid() && view.IsOnline().Valid() && !view.IsOnline().Get() { - ls := view.LastSeen().Get() - d.LastSeen = &ls - } - - if allFields { - d.AdvertisedRoutes = emptyIfNil(util.PrefixesToString(view.AnnouncedRoutes())) - d.EnabledRoutes = emptyIfNil(util.PrefixesToString(view.ApprovedRoutes().AsSlice())) - } - - return d -} - -func routesFromView(view types.NodeView) DeviceRoutes { - return DeviceRoutes{ - Advertised: emptyIfNil(util.PrefixesToString(view.AnnouncedRoutes())), - Enabled: emptyIfNil(util.PrefixesToString(view.ApprovedRoutes().AsSlice())), - } -} - -// deviceUser is the owning login. [types.NodeView.Owner] already resolves the -// tags-as-identity rule: tagged nodes present the special TaggedDevices user, -// user-owned nodes present their login, orphaned nodes present nothing. -func deviceUser(view types.NodeView) string { - owner := view.Owner() - if owner.Valid() { - return owner.Username() - } - - return "" -} - -// hostinfoOS / hostinfoVersion read Hostinfo fields, returning "" when the node -// has not reported Hostinfo yet. -func hostinfoOS(view types.NodeView) string { - if hi := view.Hostinfo(); hi.Valid() { - return hi.OS() - } - - return "" -} - -func hostinfoVersion(view types.NodeView) string { - if hi := view.Hostinfo(); hi.Valid() { - return hi.IPNVersion() - } - - return "" -} diff --git a/hscontrol/api/v2/errors.go b/hscontrol/api/v2/errors.go deleted file mode 100644 index cf5110e2f..000000000 --- a/hscontrol/api/v2/errors.go +++ /dev/null @@ -1,84 +0,0 @@ -package apiv2 - -import ( - "errors" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/state" - "gorm.io/gorm" -) - -// apiError is the Tailscale API error body. The official Tailscale Go client -// (and therefore the Terraform provider and tscli built on it) decodes 4xx/5xx -// responses into this shape. Huma's default RFC 9457 problem+json would reach -// them with an empty message, so tailscaleErrorTransformer rewrites every v2 -// error into this shape. The HTTP status is read from the response code, not -// from the body's status field. -type apiError struct { - Message string `json:"message"` - Data []apiErrorData `json:"data,omitempty"` - Status int `json:"status"` -} - -type apiErrorData struct { - User string `json:"user,omitempty"` - Errors []string `json:"errors,omitempty"` -} - -// tailscaleErrorTransformer rewrites Huma's RFC 9457 error model into the -// Tailscale error shape. It is registered on the v2 API config only, so the -// headscale-native v1 API keeps emitting problem+json. Non-error bodies pass -// through untouched. -func tailscaleErrorTransformer(_ huma.Context, _ string, v any) (any, error) { - em, ok := v.(*huma.ErrorModel) - if !ok { - return v, nil - } - - message := em.Detail - if message == "" { - message = em.Title - } - - out := apiError{Message: message, Status: em.Status} - - if len(em.Errors) > 0 { - details := make([]string, 0, len(em.Errors)) - for _, d := range em.Errors { - details = append(details, d.Error()) - } - - out.Data = []apiErrorData{{Errors: details}} - } - - return out, nil -} - -// mapError translates a state/db-layer error into a Huma HTTP error -// (not-found→404, invalid input→400, everything else→500). The transformer -// then reshapes it into the Tailscale body. -func mapError(msg string, err error) error { - if err == nil { - return nil - } - - switch { - case errors.Is(err, gorm.ErrRecordNotFound), - errors.Is(err, db.ErrPreAuthKeyNotFound), - errors.Is(err, db.ErrUserNotFound), - errors.Is(err, state.ErrNodeNotFound): - return huma.Error404NotFound(msg, err) - - case errors.Is(err, db.ErrPreAuthKeyNotTaggedOrOwned), - errors.Is(err, db.ErrPreAuthKeyACLTagInvalid), - errors.Is(err, state.ErrGivenNameInvalid), - errors.Is(err, state.ErrGivenNameTaken), - errors.Is(err, state.ErrNodeNameNotUnique), - errors.Is(err, state.ErrRequestedTagsInvalidOrNotPermitted): - return huma.Error400BadRequest(msg, err) - - default: - return huma.Error500InternalServerError(msg, err) - } -} diff --git a/hscontrol/api/v2/guard_test.go b/hscontrol/api/v2/guard_test.go deleted file mode 100644 index 7d52e0c79..000000000 --- a/hscontrol/api/v2/guard_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package apiv2 - -import ( - "testing" - - "github.com/danielgtaylor/huma/v2" - "github.com/go-chi/chi/v5" - "github.com/juanfont/headscale/hscontrol/scope" -) - -// selfEnforcedKeyOps are the authenticated operations that intentionally declare -// NO static scope because they multiplex on keyType and authorize inside the -// handler via requireKeyScope (see keys.go). Every other authenticated operation -// must declare a scope. -var selfEnforcedKeyOps = map[string]bool{ - "POST /api/v2/tailnet/{tailnet}/keys": true, - "GET /api/v2/tailnet/{tailnet}/keys": true, - "GET /api/v2/tailnet/{tailnet}/keys/{keyId}": true, - "DELETE /api/v2/tailnet/{tailnet}/keys/{keyId}": true, -} - -// TestEveryAuthenticatedOperationDeclaresScope is the structural guarantee that no -// v2 operation ships unprotected: any operation that requires authentication -// (non-empty Security) must either declare a required scope via requireScope, or -// be one of the keyType-multiplexed keys operations that self-enforce. A new -// operation added without scope protection fails this test. -func TestEveryAuthenticatedOperationDeclaresScope(t *testing.T) { - api := NewAPI(chi.NewMux(), Backend{}) - - for path, item := range api.OpenAPI().Paths { - for method, op := range humaOperations(item) { - if op == nil || len(op.Security) == 0 { - continue // unregistered method or a public operation - } - - key := method + " " + path - if selfEnforcedKeyOps[key] { - continue - } - - if _, ok := op.Metadata[scopeMetaKey].(scope.Scope); !ok { - t.Errorf("operation %q is authenticated but declares no required scope; "+ - "wrap it in requireScope, or add it to selfEnforcedKeyOps if it "+ - "authorizes inside the handler", key) - } - } - } -} - -func humaOperations(item *huma.PathItem) map[string]*huma.Operation { - return map[string]*huma.Operation{ - "GET": item.Get, - "POST": item.Post, - "PUT": item.Put, - "DELETE": item.Delete, - "PATCH": item.Patch, - } -} diff --git a/hscontrol/api/v2/helpers.go b/hscontrol/api/v2/helpers.go deleted file mode 100644 index aad0392d0..000000000 --- a/hscontrol/api/v2/helpers.go +++ /dev/null @@ -1,69 +0,0 @@ -package apiv2 - -import ( - "math" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" -) - -// emptyIfNil ensures a slice serializes as [] rather than null, which the -// Tailscale clients expect for empty list fields. -func emptyIfNil(s []string) []string { - if s == nil { - return []string{} - } - - return s -} - -// parseID parses a decimal entity id from a path segment. A non-numeric id is -// simply an unknown entity, surfaced as a 404 naming subject (e.g. "auth key"), -// so the Tailscale SDK's IsNotFound behaves. -func parseID(rawID, subject string) (uint64, error) { - id, err := strconv.ParseUint(rawID, util.Base10, util.BitSize64) - if err != nil { - return 0, huma.Error404NotFound(subject + " not found") - } - - return id, nil -} - -// parseNodeID parses a device id path segment into a [types.NodeID]. -func parseNodeID(rawID string) (types.NodeID, error) { - id, err := parseID(rawID, "device") - if err != nil { - return 0, err - } - - return types.NodeID(id), nil -} - -// timeOrZero dereferences a time pointer, returning the zero time for nil. -func timeOrZero(t *time.Time) time.Time { - if t == nil { - return time.Time{} - } - - return *t -} - -// expirySeconds reports the lifetime between created and expires in whole -// seconds, the unit the Tailscale spec documents for the response field. The -// lifetime is rounded because the stored expiration is stamped a hair before -// CreatedAt, so an 86400s request would otherwise read back as 86399. -func expirySeconds(created, expires *time.Time) int64 { - if created == nil || expires == nil { - return 0 - } - - secs := int64(math.Round(expires.Sub(*created).Seconds())) - if secs < 0 { - return 0 - } - - return secs -} diff --git a/hscontrol/api/v2/keys.go b/hscontrol/api/v2/keys.go deleted file mode 100644 index 552109136..000000000 --- a/hscontrol/api/v2/keys.go +++ /dev/null @@ -1,593 +0,0 @@ -package apiv2 - -import ( - "context" - "net/http" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" -) - -func init() { - registrations = append(registrations, registerKeys) -} - -// defaultExpiry is the auth-key lifetime when the request omits expirySeconds: -// 90 days, matching Tailscale and the Terraform provider default. -const defaultExpiry = 90 * 24 * time.Hour - -const ( - // keyTypeAuth is a machine auth key (Headscale pre-auth key); the default. - keyTypeAuth = "auth" - // keyTypeClient is an OAuth client (client-credentials). Multiplexed onto the - // keys resource exactly as Tailscale does. - keyTypeClient = "client" -) - -// KeyCapabilities maps a resource to the actions a key permits. Headscale -// populates only devices.create (auth keys); the named types (vs Tailscale's -// anonymous nesting) give Huma stable schema names. -type KeyCapabilities struct { - Devices KeyDeviceCapabilities `json:"devices"` -} - -type KeyDeviceCapabilities struct { - Create KeyDeviceCreateCapabilities `json:"create"` -} - -type KeyDeviceCreateCapabilities struct { - Reusable bool `json:"reusable"` - Ephemeral bool `json:"ephemeral"` - Preauthorized bool `json:"preauthorized"` - // Tags is not nullable:"false": the Tailscale clients send "tags":null for - // an untagged key, which must be accepted. The response always emits [] via - // emptyIfNil. - Tags []string `json:"tags"` -} - -// CreateKeyRequest is the POST body. It is multiplexed by keyType: "auth" -// (default) creates a machine auth key from Capabilities; "client" creates an -// OAuth client from the top-level Scopes and Tags. expirySeconds is plain -// seconds (unlike the response, see Key). -type CreateKeyRequest struct { - KeyType string `doc:"Key kind: \"auth\" (default) or \"client\" (OAuth client)." json:"keyType,omitempty"` - // Capabilities is optional: an auth key carries device-create capabilities, - // but an OAuth client (keyType:"client") has none and the Tailscale clients - // omit the field entirely, so it must not be required. - Capabilities *KeyCapabilities `json:"capabilities,omitempty"` - ExpirySeconds int64 `doc:"Lifetime in seconds; defaults to 90 days. Auth keys only." json:"expirySeconds,omitempty"` - Description string `json:"description,omitempty" maxLength:"50"` - // Scopes and Tags are top-level and apply only to keyType "client" (an OAuth - // client). Auth-key tags live under Capabilities.Devices.Create.Tags. - Scopes []string `doc:"OAuth scopes granted to the client. keyType=client only." json:"scopes,omitempty"` - Tags []string `doc:"Tags the client may assign. keyType=client only." json:"tags,omitempty"` -} - -// Key is the Tailscale key response, shared by auth keys and OAuth clients. -// expirySeconds is emitted in seconds to match the Tailscale spec; the secret -// key is present only at creation. -type Key struct { - ID string `json:"id"` - KeyType string `json:"keyType"` - Key string `json:"key,omitempty"` - Description string `json:"description,omitempty"` - ExpirySeconds int64 `json:"expirySeconds,omitempty"` - Created time.Time `json:"created"` - Expires *time.Time `json:"expires,omitempty"` - Revoked *time.Time `json:"revoked,omitempty"` - Invalid bool `json:"invalid"` - Capabilities KeyCapabilities `json:"capabilities"` - Scopes []string `json:"scopes,omitempty"` - Tags []string `json:"tags,omitempty"` - UserID string `json:"userId,omitempty"` -} - -type ( - createKeyInput struct { - Tailnet string `doc:"Tailnet; must be \"-\" (the single Headscale tailnet)." path:"tailnet"` - Body CreateKeyRequest - } - - listKeysInput struct { - Tailnet string `path:"tailnet"` - All bool `doc:"Accepted for compatibility; Headscale returns all keys." query:"all"` - } - - keyByIDInput struct { - Tailnet string `path:"tailnet"` - KeyID string `path:"keyId"` - } - - keyOutput struct { - Body Key - } - - listKeysOutput struct { - Body struct { - Keys []Key `json:"keys" nullable:"false"` - } - } - - deleteKeyOutput struct { - Body struct{} - } -) - -// The keys resource is multiplexed by keyType (auth key vs OAuth client), so the -// scope an operation requires depends on the request rather than being fixed. -// requireScope (which the middleware enforces statically) is therefore omitted -// here; each handler authorizes via requireKeyScope once the kind is known. -func registerKeys(api huma.API, b Backend) { - keysTags := []string{"Keys", "Tailscale compat"} - - huma.Register(api, huma.Operation{ - OperationID: "createKey", - Method: http.MethodPost, - Path: "/api/v2/tailnet/{tailnet}/keys", - Summary: "Create an auth key or OAuth client", - Description: "Requires the `auth_keys` scope for an auth key, or `oauth_keys` for an OAuth client (an admin API key is all-access).", - Tags: keysTags, - Security: security, - Errors: []int{ - http.StatusBadRequest, - http.StatusUnauthorized, - http.StatusForbidden, - http.StatusNotFound, - }, - }, func(ctx context.Context, in *createKeyInput) (*keyOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - if in.Body.KeyType == keyTypeClient { - return createOAuthClient(ctx, b, in.Body) - } - - return createAuthKey(ctx, b, in.Body) - }) - - huma.Register(api, huma.Operation{ - OperationID: "listKeys", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/keys", - Summary: "List auth keys and OAuth clients", - Description: "A token sees the kinds it can read: `auth_keys:read` for auth keys, `oauth_keys:read` for OAuth clients (an admin API key sees all).", - Tags: keysTags, - Security: security, - Errors: []int{ - http.StatusUnauthorized, - http.StatusForbidden, - http.StatusNotFound, - }, - }, func(ctx context.Context, in *listKeysInput) (*listKeysOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - scopes, isOAuth := principalScopes(ctx) - - out := &listKeysOutput{} - out.Body.Keys = []Key{} - - // A token sees the key kinds it has read scope for; an admin key sees all. - if !isOAuth || scope.Grants(scope.Parse(scopes), scope.AuthKeysRead) { - keys, err := b.State.ListPreAuthKeys() - if err != nil { - return nil, huma.Error500InternalServerError("listing auth keys", err) - } - - for i := range keys { - out.Body.Keys = append(out.Body.Keys, keyFromStored(&keys[i])) - } - } - - if !isOAuth || scope.Grants(scope.Parse(scopes), scope.OAuthKeysRead) { - clients, err := b.State.ListOAuthClients() - if err != nil { - return nil, huma.Error500InternalServerError("listing oauth clients", err) - } - - for i := range clients { - out.Body.Keys = append(out.Body.Keys, oauthClientToKey(&clients[i], "")) - } - } - - return out, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "getKey", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/keys/{keyId}", - Summary: "Get an auth key or OAuth client", - Description: "Requires `auth_keys:read` for an auth key, or `oauth_keys:read` for an OAuth client (an admin API key is all-access).", - Tags: keysTags, - Security: security, - Errors: []int{ - http.StatusUnauthorized, - http.StatusForbidden, - http.StatusNotFound, - }, - }, func(ctx context.Context, in *keyByIDInput) (*keyOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - // An OAuth client id is a hex string distinct from a numeric auth-key id, - // so a client lookup that hits is authoritative; otherwise fall through to - // the auth-key path. The lookup is gated on the caller actually holding - // oauth_keys:read so a token without it cannot tell a real client id (403) - // from an unknown key (404) — i.e. no client-existence oracle. - if requireKeyScope(ctx, scope.OAuthKeysRead) == nil { - client, err := b.State.GetOAuthClientByClientID(in.KeyID) - if err == nil { - return &keyOutput{Body: oauthClientToKey(client, "")}, nil - } - } - - err = requireKeyScope(ctx, scope.AuthKeysRead) - if err != nil { - return nil, err - } - - key, err := findKeyByID(b, in.KeyID) - if err != nil { - return nil, err - } - - return &keyOutput{Body: keyFromStored(key)}, nil - }) - - huma.Register(api, huma.Operation{ - OperationID: "deleteKey", - Method: http.MethodDelete, - Path: "/api/v2/tailnet/{tailnet}/keys/{keyId}", - Summary: "Delete an auth key or OAuth client", - Description: "Requires the `auth_keys` scope for an auth key, or `oauth_keys` for an OAuth client (an admin API key is all-access).", - Tags: keysTags, - Security: security, - DefaultStatus: http.StatusOK, - Errors: []int{ - http.StatusUnauthorized, - http.StatusForbidden, - http.StatusNotFound, - }, - }, func(ctx context.Context, in *keyByIDInput) (*deleteKeyOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - // Gated on oauth_keys (write) for the same no-existence-oracle reason as - // getKey: a token without it must not learn that an id is an OAuth client. - if requireKeyScope(ctx, scope.OAuthKeys) == nil { - _, err = b.State.GetOAuthClientByClientID(in.KeyID) - if err == nil { - err = b.State.RevokeOAuthClient(in.KeyID) - if err != nil { - return nil, mapError("deleting oauth client", err) - } - - return &deleteKeyOutput{}, nil - } - } - - err = requireKeyScope(ctx, scope.AuthKeys) - if err != nil { - return nil, err - } - - id, err := parseID(in.KeyID, "auth key") - if err != nil { - return nil, err - } - - // Tailscale's DELETE revokes the key but keeps it retrievable (invalid) - // rather than destroying it; the collector reaps it after the retention - // window. - err = b.State.RevokePreAuthKey(id) - if err != nil { - return nil, mapError("revoking auth key", err) - } - - return &deleteKeyOutput{}, nil - }) -} - -// requireKeyScope authorizes a keys operation for an OAuth access token. An admin -// API key carries no OAuth scopes and is all-access, so it always passes. -func requireKeyScope(ctx context.Context, need scope.Scope) error { - scopes, isOAuth := principalScopes(ctx) - if !isOAuth { - return nil - } - - if !scope.Grants(scope.Parse(scopes), need) { - return huma.Error403Forbidden("token is missing the required scope " + string(need)) - } - - return nil -} - -// createAuthKey creates a machine auth key (pre-auth key). Ownership: tags -> a -// tagged key; no tags -> owned by the API key's user. An OAuth access token must -// mint tagged keys, and each tag must be within the token's grant. -func createAuthKey(ctx context.Context, b Backend, body CreateKeyRequest) (*keyOutput, error) { - err := requireKeyScope(ctx, scope.AuthKeys) - if err != nil { - return nil, err - } - - var create KeyDeviceCreateCapabilities - if body.Capabilities != nil { - create = body.Capabilities.Devices.Create - } - - tokenTags, isOAuth := principalTags(ctx) - - var userID *types.UserID - - switch { - case len(create.Tags) > 0: - // A key minted by an OAuth token may only carry tags within the token's - // grant (held directly, or owned by a held tag) and defined in policy, - // matching SetNodeTags. An admin key keeps the historical behaviour of - // validating only tag syntax (db.validateACLTags). - if isOAuth { - for _, tag := range create.Tags { - if !b.State.TagExists(tag) { - return nil, huma.Error400BadRequest("tag " + tag + " is not defined in policy") - } - - if !b.State.TagOwnedByTags(tag, tokenTags) { - return nil, huma.Error403Forbidden( - "token may not assign tag " + tag, - ) - } - } - } - - case isOAuth: - // OAuth-minted keys are tailnet/tag-owned; an untagged (user-owned) key - // cannot be created from a token. - return nil, huma.Error403Forbidden("an OAuth client must create tagged auth keys") - - default: - uid, ok := ownerUser(ctx) - if !ok { - return nil, huma.Error400BadRequest( - "an auth key without tags must be created with a user-owned API key", - ) - } - - userID = &uid - } - - expiration := time.Now().Add(expiryDuration(body.ExpirySeconds)) - - pak, err := b.State.CreatePreAuthKey( - userID, - create.Reusable, - create.Ephemeral, - &expiration, - create.Tags, - ) - if err != nil { - return nil, mapError("creating auth key", err) - } - - if body.Description != "" { - err := b.State.SetPreAuthKeyDescription(pak.ID, body.Description) - if err != nil { - return nil, huma.Error500InternalServerError("setting auth key description", err) - } - } - - return &keyOutput{Body: keyFromNew(pak, body)}, nil -} - -// createOAuthClient creates an OAuth client (keyType:"client"). The client secret -// is returned once, here. -func createOAuthClient(ctx context.Context, b Backend, body CreateKeyRequest) (*keyOutput, error) { - err := requireKeyScope(ctx, scope.OAuthKeys) - if err != nil { - return nil, err - } - - if len(body.Scopes) == 0 { - return nil, huma.Error400BadRequest("an OAuth client must declare at least one scope") - } - - // Tailscale: tags are mandatory when the scopes include devices:core or - // auth_keys, because such a client mints tagged, tailnet-owned credentials. - if scope.RequiresTags(scope.Parse(body.Scopes)) && len(body.Tags) == 0 { - return nil, huma.Error400BadRequest( - "tags are required when scopes include devices:core or auth_keys", - ) - } - - // A client created by an OAuth token may not be granted authority the token - // lacks: its scopes must each be within the token's grant, and its tags within - // the token's tags and defined in policy (matching SetNodeTags). Otherwise an - // oauth_keys token could mint an all-access client and escalate. An admin API - // key (not an OAuth token) is unrestricted and keeps the historical tag - // behaviour (syntax-only validation). - if tokenScopes, isOAuth := principalScopes(ctx); isOAuth { - for _, s := range body.Scopes { - if !scope.Grants(scope.Parse(tokenScopes), scope.Scope(s)) { - return nil, huma.Error403Forbidden( - "client may not be granted scope " + s + " beyond the creating token", - ) - } - } - - tokenTags, _ := principalTags(ctx) - - for _, tag := range body.Tags { - if !b.State.TagExists(tag) { - return nil, huma.Error400BadRequest("tag " + tag + " is not defined in policy") - } - - if !b.State.TagOwnedByTags(tag, tokenTags) { - return nil, huma.Error403Forbidden( - "client may not be granted tag " + tag + " beyond the creating token", - ) - } - } - } - - var creator *uint - - if uid, ok := ownerUser(ctx); ok { - u := uint(uid) - creator = &u - } - - secret, client, err := b.State.CreateOAuthClient(body.Scopes, body.Tags, body.Description, creator) - if err != nil { - return nil, mapError("creating oauth client", err) - } - - return &keyOutput{Body: oauthClientToKey(client, secret)}, nil -} - -// findKeyByID looks up a stored pre-auth key by its (stringified) id with a -// direct by-id query; an unknown id surfaces as gorm.ErrRecordNotFound, which -// mapError turns into a 404. -func findKeyByID(b Backend, rawID string) (*types.PreAuthKey, error) { - id, err := parseID(rawID, "auth key") - if err != nil { - return nil, err - } - - pak, err := b.State.GetPreAuthKeyByID(id) - if err != nil { - return nil, mapError("looking up auth key", err) - } - - return pak, nil -} - -// keyFromNew builds the create response from the freshly created key. The -// plaintext secret is returned only here. preauthorized is reported true to -// match keyFromStored: Headscale always authorizes pre-auth-key nodes, so the -// create and read paths must agree or the Terraform provider sees a diff. -func keyFromNew(pak *types.PreAuthKeyNew, req CreateKeyRequest) Key { - var create KeyDeviceCreateCapabilities - if req.Capabilities != nil { - create = req.Capabilities.Devices.Create - } - - key := Key{ - ID: pak.StringID(), - KeyType: keyTypeAuth, - Key: pak.Key, - Description: req.Description, - Created: timeOrZero(pak.CreatedAt), - Capabilities: capabilities( - create.Reusable, - create.Ephemeral, - true, - pak.Tags, - ), - Tags: pak.Tags, - } - - if pak.Expiration != nil { - key.Expires = pak.Expiration - key.ExpirySeconds = expirySeconds(pak.CreatedAt, pak.Expiration) - } - - // A tagged key presents no owner; a user-owned key reports its user id. - if len(pak.Tags) == 0 && pak.User != nil { - key.UserID = pak.User.StringID() - } - - return key -} - -// keyFromStored builds the get/list response from a stored key. The secret is -// never returned here. preauthorized is reported true: Headscale has no separate -// device-approval step, so every pre-auth key authorizes its nodes. Reporting it -// stably keeps the Terraform provider from seeing a forced-replacement diff. -func keyFromStored(pak *types.PreAuthKey) Key { - key := Key{ - ID: pak.StringID(), - KeyType: keyTypeAuth, - Description: pak.Description, - Created: timeOrZero(pak.CreatedAt), - Invalid: pak.Validate() != nil, - Capabilities: capabilities(pak.Reusable, pak.Ephemeral, true, pak.Tags), - Tags: pak.Tags, - } - - if pak.Expiration != nil { - key.Expires = pak.Expiration - key.ExpirySeconds = expirySeconds(pak.CreatedAt, pak.Expiration) - } - - if pak.Revoked != nil { - key.Revoked = pak.Revoked - } - - if len(pak.Tags) == 0 && pak.User != nil { - key.UserID = pak.User.StringID() - } - - return key -} - -// oauthClientToKey builds the keys response for an OAuth client. secret is the -// plaintext client secret, set only on the create response and empty on get/list -// (the secret is never re-exposed). -func oauthClientToKey(client *types.OAuthClient, secret string) Key { - key := Key{ - ID: client.ClientID, - KeyType: keyTypeClient, - Key: secret, - Description: client.Description, - Created: timeOrZero(client.CreatedAt), - Scopes: emptyIfNil(client.Scopes), - Tags: emptyIfNil(client.Tags), - } - - if client.Revoked != nil { - key.Revoked = client.Revoked - key.Invalid = true - } - - if client.UserID != nil { - key.UserID = strconv.FormatUint(uint64(*client.UserID), util.Base10) - } - - return key -} - -func capabilities( - reusable, ephemeral, preauthorized bool, - tags []string, -) KeyCapabilities { - return KeyCapabilities{ - Devices: KeyDeviceCapabilities{Create: KeyDeviceCreateCapabilities{ - Reusable: reusable, - Ephemeral: ephemeral, - Preauthorized: preauthorized, - Tags: emptyIfNil(tags), - }}, - } -} - -func expiryDuration(seconds int64) time.Duration { - if seconds <= 0 { - return defaultExpiry - } - - return time.Duration(seconds) * time.Second -} diff --git a/hscontrol/api/v2/oauth.go b/hscontrol/api/v2/oauth.go deleted file mode 100644 index 330c94f96..000000000 --- a/hscontrol/api/v2/oauth.go +++ /dev/null @@ -1,178 +0,0 @@ -package apiv2 - -import ( - "encoding/json" - "net/http" - "slices" - "strings" - "time" - - "github.com/go-chi/chi/v5" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" -) - -// accessTokenTTL is the fixed lifetime of a minted access token, matching -// Tailscale's non-configurable one hour. -const accessTokenTTL = time.Hour - -// registerOAuthToken mounts POST /api/v2/oauth/token on the router. It is a plain -// handler, not a Huma operation: it consumes application/x-www-form-urlencoded -// and emits RFC 6749 OAuth2 error bodies ({"error","error_description"}), neither -// of which fits Huma's JSON-in / Tailscale-error-out machinery. -// ponytail: one bespoke OAuth endpoint isn't worth bending Huma around. -func registerOAuthToken(router chi.Router, b Backend) { - router.Post("/api/v2/oauth/token", oauthTokenHandler(b)) -} - -// tokenResponse is the OAuth 2.0 client-credentials success body. -type tokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - Scope string `json:"scope,omitempty"` -} - -// oauthTokenHandler implements the client-credentials grant (RFC 6749 §4.4): -// authenticate the client, optionally narrow the granted scopes/tags, and mint a -// short-lived bearer token. -func oauthTokenHandler(b Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - if err != nil { - writeOAuthError(w, http.StatusBadRequest, "invalid_request", "could not parse request body") - - return - } - - // grant_type defaults to client_credentials: Tailscale's documented curl - // omits it, and the x/oauth2 client always sends it. - if gt := r.PostForm.Get("grant_type"); gt != "" && gt != "client_credentials" { - writeOAuthError(w, http.StatusBadRequest, "unsupported_grant_type", - "only the client_credentials grant is supported") - - return - } - - // Credentials may arrive in the body or as HTTP Basic (the x/oauth2 - // auto-detect probes Basic first). The secret embeds the client id, so a - // separate client_id is not required. - secret := r.PostForm.Get("client_secret") - if secret == "" { - if _, pass, ok := r.BasicAuth(); ok { - secret = pass - } - } - - if secret == "" { - writeOAuthError(w, http.StatusUnauthorized, "invalid_client", "missing client credentials") - - return - } - - client, err := b.State.AuthenticateOAuthClient(secret) - if err != nil { - writeOAuthError(w, http.StatusUnauthorized, "invalid_client", "invalid client credentials") - - return - } - - // Optional space-delimited scope/tags narrow the token to a subset of the - // client's grant. - scopes, badScope, ok := narrowScopes(client.Scopes, strings.Fields(r.PostForm.Get("scope"))) - if !ok { - writeOAuthError(w, http.StatusBadRequest, "invalid_scope", - "scope "+badScope+" is not granted to this client") - - return - } - - tags, badTag, ok := narrowTags(client, strings.Fields(r.PostForm.Get("tags"))) - if !ok { - writeOAuthError(w, http.StatusBadRequest, "invalid_target", - "tag "+badTag+" is not granted to this client") - - return - } - - expiry := time.Now().Add(accessTokenTTL) - - tokenStr, _, err := b.State.MintAccessToken(client.ClientID, scopes, tags, &expiry) - if err != nil { - writeOAuthError(w, http.StatusInternalServerError, "server_error", "could not mint access token") - - return - } - - writeJSON(w, http.StatusOK, tokenResponse{ - AccessToken: tokenStr, - TokenType: "Bearer", - ExpiresIn: int(accessTokenTTL.Seconds()), - Scope: strings.Join(scopes, " "), - }) - } -} - -// narrowScopes returns the requested scopes if each is granted by the client (an -// empty request means "the client's full grant"), otherwise the offending scope -// and false. A client holding a broad scope (e.g. "all") may mint a token limited -// to a narrower one. -func narrowScopes(granted, requested []string) ([]string, string, bool) { - if len(requested) == 0 { - return granted, "", true - } - - for _, req := range requested { - if !scope.Grants(scope.Parse(granted), scope.Scope(req)) { - return nil, req, false - } - } - - return requested, "", true -} - -// narrowTags returns the requested tags if each is within the client's grant (an -// empty request means "the client's full tag set"), otherwise the offending tag -// and false. A client with the "all" scope may assign any tag, matching Tailscale. -func narrowTags(client *types.OAuthClient, requested []string) ([]string, string, bool) { - if len(requested) == 0 { - return client.Tags, "", true - } - - allScope := slices.Contains(client.Scopes, string(scope.All)) - - for _, req := range requested { - // Reject malformed tags at the trust boundary. A client's own tags are - // validated at creation, so this only matters for an "all"-scope client, - // whose tags would otherwise skip the membership check below and flow - // unvalidated into auth-key creation. - if !strings.HasPrefix(req, "tag:") { - return nil, req, false - } - - if !allScope && !slices.Contains(client.Tags, req) { - return nil, req, false - } - } - - return requested, "", true -} - -func writeJSON(w http.ResponseWriter, status int, v any) { - w.Header().Set("Content-Type", "application/json") - // Token responses carry bearer credentials; RFC 6749 §5.1 forbids caching - // them. writeJSON serves only the token endpoint, so set it unconditionally. - w.Header().Set("Cache-Control", "no-store") - w.Header().Set("Pragma", "no-cache") - w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(v) //nolint:errchkjson // best-effort response write of a known-safe value -} - -// writeOAuthError emits an RFC 6749 §5.2 error body, which the x/oauth2 client -// parses into its RetrieveError. -func writeOAuthError(w http.ResponseWriter, status int, code, desc string) { - writeJSON(w, status, map[string]string{ - "error": code, - "error_description": desc, - }) -} diff --git a/hscontrol/api/v2/oauth_test.go b/hscontrol/api/v2/oauth_test.go deleted file mode 100644 index 1abbacf87..000000000 --- a/hscontrol/api/v2/oauth_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package apiv2 - -import ( - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" -) - -// TestNarrowTagsRejectsMalformed asserts the token endpoint validates tag -// format at the trust boundary. An "all"-scope client may assign any tag, but a -// malformed tag (missing the "tag:" prefix) must still be rejected rather than -// flowing unvalidated into auth-key creation. -func TestNarrowTagsRejectsMalformed(t *testing.T) { - all := &types.OAuthClient{Scopes: []string{"all"}} - - _, bad, ok := narrowTags(all, []string{"not-a-tag"}) - assert.False(t, ok, "malformed tag must be rejected even with all scope") - assert.Equal(t, "not-a-tag", bad) - - got, _, ok := narrowTags(all, []string{"tag:anything"}) - assert.True(t, ok, "all scope may assign any well-formed tag") - assert.Equal(t, []string{"tag:anything"}, got) - - // A scoped client may only assign its own well-formed tags. - scoped := &types.OAuthClient{Scopes: []string{"auth_keys"}, Tags: []string{"tag:ci"}} - - _, bad, ok = narrowTags(scoped, []string{"tag:other"}) - assert.False(t, ok) - assert.Equal(t, "tag:other", bad) -} diff --git a/hscontrol/api/v2/settings.go b/hscontrol/api/v2/settings.go deleted file mode 100644 index 262ec554c..000000000 --- a/hscontrol/api/v2/settings.go +++ /dev/null @@ -1,100 +0,0 @@ -package apiv2 - -import ( - "context" - "encoding/json" - "net/http" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" -) - -func init() { - registrations = append(registrations, registerSettings) -} - -// TailnetSettings is the Tailscale tailnet-settings response. Headscale's config -// is file-based and mostly not runtime-mutable, so only a few fields carry a -// real value; the rest report the default "off". -type TailnetSettings struct { - ACLsExternallyManagedOn bool `json:"aclsExternallyManagedOn"` - ACLsExternalLink string `json:"aclsExternalLink"` - - DevicesApprovalOn bool `json:"devicesApprovalOn"` - DevicesAutoUpdatesOn bool `json:"devicesAutoUpdatesOn"` - DevicesKeyDurationDays int `json:"devicesKeyDurationDays"` - - UsersApprovalOn bool `json:"usersApprovalOn"` - UsersRoleAllowedToJoinExternalTailnets string `json:"usersRoleAllowedToJoinExternalTailnets"` - - NetworkFlowLoggingOn bool `json:"networkFlowLoggingOn"` - RegionalRoutingOn bool `json:"regionalRoutingOn"` - PostureIdentityCollectionOn bool `json:"postureIdentityCollectionOn"` - HTTPSEnabled bool `json:"httpsEnabled"` -} - -type ( - getSettingsInput struct { - Tailnet string `path:"tailnet"` - } - settingsOutput struct { - Body TailnetSettings - } - patchSettingsInput struct { - Tailnet string `path:"tailnet"` - // Accepted and ignored; updating settings is not supported. - Body json.RawMessage - } -) - -func registerSettings(api huma.API, b Backend) { - settingsTags := []string{"TailnetSettings", "Tailscale compat"} - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "getTailnetSettings", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/settings", - Summary: "Get tailnet settings", - Tags: settingsTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.FeatureSettingsRead), func(ctx context.Context, in *getSettingsInput) (*settingsOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - cfg := b.Cfg - - return &settingsOutput{Body: TailnetSettings{ - // File-mode policy is genuinely externally managed (read-only via API). - ACLsExternallyManagedOn: cfg.Policy.Mode == types.PolicyModeFile, - DevicesKeyDurationDays: int(cfg.Node.Expiry / (24 * time.Hour)), - HTTPSEnabled: cfg.TLS.CertPath != "" || cfg.TLS.LetsEncrypt.Hostname != "", - UsersRoleAllowedToJoinExternalTailnets: "none", - }}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "updateTailnetSettings", - Method: http.MethodPatch, - Path: "/api/v2/tailnet/{tailnet}/settings", - Summary: "Update tailnet settings", - Tags: settingsTags, - Security: security, - // The body is accepted but ignored; skip validation. - SkipValidateBody: true, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusNotImplemented}, - }, scope.FeatureSettings), func(ctx context.Context, in *patchSettingsInput) (*settingsOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - return nil, huma.Error501NotImplemented( - "updating tailnet settings is not supported by Headscale", - ) - }) -} diff --git a/hscontrol/api/v2/users.go b/hscontrol/api/v2/users.go deleted file mode 100644 index 2890f432e..000000000 --- a/hscontrol/api/v2/users.go +++ /dev/null @@ -1,176 +0,0 @@ -package apiv2 - -import ( - "context" - "net/http" - "strconv" - "time" - - "github.com/danielgtaylor/huma/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/juanfont/headscale/hscontrol/types" -) - -func init() { - registrations = append(registrations, registerUsers) -} - -// Headscale models none of type/role/status and has a single tailnet, so these -// fields are fixed strings. Every account is an active member. -const ( - userTypeMember = "member" - userRoleMember = "member" - userStatusActive = "active" - singleTailnetID = "1" - - // tagTailscaleCompat marks operations ported from the Tailscale API. - tagTailscaleCompat = "Tailscale compat" -) - -// User is the Tailscale user response. Identity fields map from the Headscale -// user; type/role/status/tailnetId are constants (see above); the device fields -// are aggregated from the user's nodes. -type User struct { - ID string `json:"id"` - DisplayName string `json:"displayName"` - LoginName string `json:"loginName"` - ProfilePicURL string `json:"profilePicUrl"` - TailnetID string `json:"tailnetId"` - Created time.Time `json:"created"` - Type string `json:"type"` - Role string `json:"role"` - Status string `json:"status"` - DeviceCount int `json:"deviceCount"` - LastSeen time.Time `json:"lastSeen"` - CurrentlyConnected bool `json:"currentlyConnected"` -} - -type ( - userByIDInput struct { - UserID string `doc:"User id (the decimal user id)." path:"id"` - } - listUsersInput struct { - Tailnet string `doc:"Tailnet; must be \"-\" (the single Headscale tailnet)." path:"tailnet"` - Type string `doc:"Filter by user type; Headscale users are all \"member\"." query:"type"` - Role string `doc:"Filter by user role; Headscale users are all \"member\"." query:"role"` - } - - userOutput struct{ Body User } - listUsersOutput struct { - Body struct { - Users []User `json:"users" nullable:"false"` - } - } -) - -func registerUsers(api huma.API, b Backend) { - usersTags := []string{"Users", tagTailscaleCompat} - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "getUser", - Method: http.MethodGet, - Path: "/api/v2/users/{id}", - Summary: "Get a user", - Tags: usersTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.UsersRead), func(ctx context.Context, in *userByIDInput) (*userOutput, error) { - view, err := lookupUser(b, in.UserID) - if err != nil { - return nil, err - } - - return &userOutput{Body: userFromView(b, view)}, nil - }) - - huma.Register(api, requireScope(huma.Operation{ - OperationID: "listUsers", - Method: http.MethodGet, - Path: "/api/v2/tailnet/{tailnet}/users", - Summary: "List users", - Tags: usersTags, - Security: security, - Errors: []int{http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound}, - }, scope.UsersRead), func(ctx context.Context, in *listUsersInput) (*listUsersOutput, error) { - err := requireDefaultTailnet(in.Tailnet) - if err != nil { - return nil, err - } - - out := &listUsersOutput{} - out.Body.Users = []User{} - - // Headscale has only "member" users. A filter for any other type/role - // matches nothing, so return the empty envelope. - if !matchesMember(in.Type) || !matchesMember(in.Role) { - return out, nil - } - - users, err := b.State.ListAllUsers() - if err != nil { - return nil, huma.Error500InternalServerError("listing users", err) - } - - out.Body.Users = make([]User, 0, len(users)) - for i := range users { - out.Body.Users = append(out.Body.Users, userFromView(b, users[i].View())) - } - - return out, nil - }) -} - -// lookupUser resolves a user id to its UserView, mapping a malformed or unknown -// id to 404 (the Tailscale SDK keys IsNotFound off the status code), exactly as -// lookupNode does for devices. -func lookupUser(b Backend, rawID string) (types.UserView, error) { - id, err := parseID(rawID, "user") - if err != nil { - return types.UserView{}, err - } - - user, err := b.State.GetUserByID(types.UserID(id)) - if err != nil { - return types.UserView{}, mapError("looking up user", err) - } - - return user.View(), nil -} - -// matchesMember reports whether an optional type/role filter selects Headscale's -// only user kind. An empty value means "no filter". -func matchesMember(filter string) bool { - return filter == "" || filter == userTypeMember -} - -// userFromView maps a Headscale user onto the Tailscale User through the -// UserView accessors. deviceCount, lastSeen, and currentlyConnected are -// aggregated from the user's nodes in the NodeStore. -func userFromView(b Backend, view types.UserView) User { - u := User{ - ID: strconv.FormatUint(uint64(view.ID()), 10), - DisplayName: view.Display(), - LoginName: view.Username(), - ProfilePicURL: view.ProfilePicURL(), - TailnetID: singleTailnetID, - Created: view.CreatedAt(), - Type: userTypeMember, - Role: userRoleMember, - Status: userStatusActive, - } - - nodes := b.State.ListNodesByUser(types.UserID(view.ID())) - u.DeviceCount = nodes.Len() - - for _, node := range nodes.All() { - if node.IsOnline().Valid() && node.IsOnline().Get() { - u.CurrentlyConnected = true - } - - if ls := node.LastSeen(); ls.Valid() && ls.Get().After(u.LastSeen) { - u.LastSeen = ls.Get() - } - } - - return u -} diff --git a/hscontrol/apiv1_apikeys_test.go b/hscontrol/apiv1_apikeys_test.go deleted file mode 100644 index 74b4634ae..000000000 --- a/hscontrol/apiv1_apikeys_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// seedAPIKey creates a single API key and returns its database ID and the -// masked display prefix the API exposes, so tests can address it by either. -func seedAPIKey(t *testing.T, app *Headscale) (uint64, string) { - t.Helper() - - _, key, err := app.state.CreateAPIKey(nil) - require.NoError(t, err) - - return key.ID, "hskey-api-" + key.Prefix + "-***" -} - -func TestAPIV1ApiKeyCreate(t *testing.T) { - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodPost, "/api/v1/apikey", []byte(`{}`)) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - APIKey string `json:"apiKey"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - // The full secret is returned exactly once, on creation. - assert.NotEmpty(t, got.APIKey) - assert.Contains(t, got.APIKey, "hskey-api-") - }) - - t.Run("creates secret", func(t *testing.T) { - // The secret is random, so it can't be captured in a golden; just assert - // success and that a secret is returned. - humaApp := createTestApp(t) - - hum := callHandler(newHumaTestHandler(humaApp), http.MethodPost, "/api/v1/apikey", []byte(`{}`)) - - assert.Equal(t, http.StatusOK, hum.status) - - var humBody struct { - APIKey string `json:"apiKey"` - } - require.NoError(t, json.Unmarshal(hum.body, &humBody)) - assert.NotEmpty(t, humBody.APIKey) - }) -} - -func TestAPIV1ApiKeyList(t *testing.T) { - t.Run("empty returns empty array", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodGet, "/api/v1/apikey", nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{"apiKeys":[]}`, string(res.body)) - }) - - t.Run("empty parity", func(t *testing.T) { - h := newAPIV1Harness(t) - h.assertParity(t, http.MethodGet, "/api/v1/apikey", nil) - }) - - t.Run("populated parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedAPIKey(t, h.app) - seedAPIKey(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/apikey", nil) - }) - - t.Run("nil timestamps emitted as null parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedAPIKey(t, h.app) - // Nil expiration: lastSeen and expiration are unset and must emit as - // JSON null. - res := h.assertParity(t, http.MethodGet, "/api/v1/apikey", nil) - - var got struct { - APIKeys []map[string]any `json:"apiKeys"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - require.Len(t, got.APIKeys, 1) - assert.Nil(t, got.APIKeys[0]["lastSeen"]) - assert.Nil(t, got.APIKeys[0]["expiration"]) - assert.Contains(t, got.APIKeys[0], "createdAt") - assert.Contains(t, got.APIKeys[0]["prefix"], "hskey-api-") - }) - - t.Run("zero expiration emitted as zero instant parity", func(t *testing.T) { - h := newAPIV1Harness(t) - // Non-nil zero expiration (as gRPC CreateApiKey does for a missing one) - // must render as the zero instant, not null. - var zero time.Time - - _, _, err := h.app.state.CreateAPIKey(&zero) - require.NoError(t, err) - - res := h.assertParity(t, http.MethodGet, "/api/v1/apikey", nil) - - var got struct { - APIKeys []map[string]any `json:"apiKeys"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - require.Len(t, got.APIKeys, 1) - assert.Equal(t, "0001-01-01T00:00:00Z", got.APIKeys[0]["expiration"]) - }) -} - -func TestAPIV1ApiKeyExpire(t *testing.T) { - t.Run("by id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - id, _ := seedAPIKey(t, h.app) - h.assertParity(t, http.MethodPost, "/api/v1/apikey/expire", - []byte(`{"id":"`+strconv.FormatUint(id, 10)+`"}`)) - }) - - t.Run("by prefix parity", func(t *testing.T) { - h := newAPIV1Harness(t) - _, prefix := seedAPIKey(t, h.app) - h.assertParity(t, http.MethodPost, "/api/v1/apikey/expire", - []byte(`{"prefix":"`+prefix+`"}`)) - }) - - t.Run("neither id nor prefix parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/apikey/expire", []byte(`{}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("both id and prefix parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/apikey/expire", - []byte(`{"id":"1","prefix":"abc"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("not found by id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/apikey/expire", []byte(`{"id":"999"}`)) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("expires the key", func(t *testing.T) { - h := newAPIV1Harness(t) - id, _ := seedAPIKey(t, h.app) - - res := h.callHuma(http.MethodPost, "/api/v1/apikey/expire", - []byte(`{"id":"`+strconv.FormatUint(id, 10)+`"}`)) - require.Equal(t, http.StatusOK, res.status) - - listRes := h.callHuma(http.MethodGet, "/api/v1/apikey", nil) - - var got struct { - APIKeys []struct { - Expiration *time.Time `json:"expiration"` - } `json:"apiKeys"` - } - require.NoError(t, json.Unmarshal(listRes.body, &got)) - require.Len(t, got.APIKeys, 1) - require.NotNil(t, got.APIKeys[0].Expiration) - assert.False(t, got.APIKeys[0].Expiration.IsZero(), "expiration should be set") - }) -} - -func TestAPIV1ApiKeyDelete(t *testing.T) { - t.Run("by prefix deletes the key", func(t *testing.T) { - h := newAPIV1Harness(t) - _, prefix := seedAPIKey(t, h.app) - - res := h.callHuma(http.MethodDelete, "/api/v1/apikey/"+prefix, nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{}`, string(res.body)) - - listRes := h.callHuma(http.MethodGet, "/api/v1/apikey", nil) - assert.JSONEq(t, `{"apiKeys":[]}`, string(listRes.body)) - }) - - t.Run("path prefix with id query is both -> 400 parity", func(t *testing.T) { - h := newAPIV1Harness(t) - id, prefix := seedAPIKey(t, h.app) - res := h.assertParity(t, http.MethodDelete, - "/api/v1/apikey/"+prefix+"?id="+strconv.FormatUint(id, 10), nil) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("not found by prefix parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/apikey/doesnotexist", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("both id and prefix parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/apikey/abc?id=1", nil) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/apikey/abc?id=notanumber", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} diff --git a/hscontrol/apiv1_auth_test.go b/hscontrol/apiv1_auth_test.go deleted file mode 100644 index 1f157f414..000000000 --- a/hscontrol/apiv1_auth_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" - "tailscale.com/types/key" -) - -// seedAuthRequest stores a pending registration auth cache entry under authID so -// an AuthRegister/Approve/Reject call has a real session to act on. Capturing -// the keys/authID at the call site keeps isolated apps identical. -func seedAuthRequest( - user string, - authID types.AuthID, - machineKey key.MachinePublic, - nodeKey key.NodePublic, - discoKey key.DiscoPublic, - hostname string, -) func(t *testing.T, app *Headscale) { - return func(t *testing.T, app *Headscale) { - t.Helper() - - app.state.CreateUserForTest(user) - - regData := &types.RegistrationData{ - MachineKey: machineKey, - NodeKey: nodeKey, - DiscoKey: discoKey, - Hostname: hostname, - Hostinfo: &tailcfg.Hostinfo{Hostname: hostname}, - } - - app.state.SetAuthCacheEntry(authID, types.NewRegisterAuthRequest(regData)) - } -} - -func TestAPIV1AuthRegister(t *testing.T) { - t.Run("happy path parity", func(t *testing.T) { - // Capture keys/authID once so both isolated apps register the identical - // node and produce byte-equal bodies. - authID := types.MustAuthID() - machineKey := key.NewMachine().Public() - nodeKey := key.NewNode().Public() - discoKey := key.NewDisco().Public() - - seed := seedAuthRequest("alice", authID, machineKey, nodeKey, discoKey, "regnode") - body := fmt.Appendf(nil, `{"user":"alice","authId":%q}`, authID.String()) - - assertParityIsolated(t, seed, http.MethodPost, "/api/v1/auth/register", body) - }) - - t.Run("happy path response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - - authID := types.MustAuthID() - seedAuthRequest( - "alice", authID, - key.NewMachine().Public(), - key.NewNode().Public(), - key.NewDisco().Public(), - "regnode", - )(t, h.app) - - body := fmt.Appendf(nil, `{"user":"alice","authId":%q}`, authID.String()) - res := h.callHuma(http.MethodPost, "/api/v1/auth/register", body) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.Node["id"]) - assert.Equal(t, "REGISTER_METHOD_CLI", got.Node["registerMethod"]) - // EmitUnpopulated parity: an unset expiry (we passed none) is null, an - // unset embedded pre-auth key is null, and repeated fields are [] - // rather than omitted. - assert.Contains(t, got.Node, "expiry") - assert.Nil(t, got.Node["expiry"]) - assert.Contains(t, got.Node, "preAuthKey") - assert.Nil(t, got.Node["preAuthKey"]) - assert.Equal(t, []any{}, got.Node["subnetRoutes"]) - assert.NotNil(t, got.Node["user"]) - }) - - // A malformed auth_id is bad input (400), matching AuthApprove/AuthReject. - t.Run("invalid auth_id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice")(t, h.app) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/register", - []byte(`{"user":"alice","authId":"not-a-valid-auth-id"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("unknown user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - body := fmt.Appendf(nil, `{"user":"ghost","authId":%q}`, types.MustAuthID().String()) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/register", body) - assertStatus(t, res, http.StatusNotFound) - }) - - // Valid auth_id but no cached session: HandleNodeFromAuthPath returns - // ErrNodeNotFoundRegistrationCache, which maps to 404. - t.Run("no pending session parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice")(t, h.app) - - body := fmt.Appendf(nil, `{"user":"alice","authId":%q}`, types.MustAuthID().String()) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/register", body) - assertStatus(t, res, http.StatusNotFound) - }) -} - -func TestAPIV1AuthApprove(t *testing.T) { - t.Run("happy path", func(t *testing.T) { - h := newAPIV1Harness(t) - - authID := types.MustAuthID() - authReq := types.NewAuthRequest() - h.app.state.SetAuthCacheEntry(authID, authReq) - - body := fmt.Appendf(nil, `{"authId":%q}`, authID.String()) - res := h.callHuma(http.MethodPost, "/api/v1/auth/approve", body) - - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{}`, string(res.body)) - - verdict := <-authReq.WaitForAuth() - assert.True(t, verdict.Accept(), "approve must finish the session with a passing verdict") - }) - - // Malformed auth_id: AuthApprove returns codes.InvalidArgument → 400. - t.Run("invalid auth_id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/approve", - []byte(`{"authId":"not-a-valid-auth-id"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - // Well-formed auth_id with no pending session: codes.NotFound → 404. - t.Run("no pending session parity", func(t *testing.T) { - h := newAPIV1Harness(t) - body := fmt.Appendf(nil, `{"authId":%q}`, types.MustAuthID().String()) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/approve", body) - assertStatus(t, res, http.StatusNotFound) - }) -} - -func TestAPIV1AuthReject(t *testing.T) { - t.Run("happy path", func(t *testing.T) { - h := newAPIV1Harness(t) - - authID := types.MustAuthID() - authReq := types.NewAuthRequest() - h.app.state.SetAuthCacheEntry(authID, authReq) - - body := fmt.Appendf(nil, `{"authId":%q}`, authID.String()) - res := h.callHuma(http.MethodPost, "/api/v1/auth/reject", body) - - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{}`, string(res.body)) - - verdict := <-authReq.WaitForAuth() - assert.False(t, verdict.Accept(), "reject must finish the session with a failing verdict") - }) - - t.Run("invalid auth_id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/reject", - []byte(`{"authId":"not-a-valid-auth-id"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("no pending session parity", func(t *testing.T) { - h := newAPIV1Harness(t) - body := fmt.Appendf(nil, `{"authId":%q}`, types.MustAuthID().String()) - res := h.assertParity(t, http.MethodPost, "/api/v1/auth/reject", body) - assertStatus(t, res, http.StatusNotFound) - }) -} diff --git a/hscontrol/apiv1_authmw_test.go b/hscontrol/apiv1_authmw_test.go deleted file mode 100644 index 814f839d0..000000000 --- a/hscontrol/apiv1_authmw_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package hscontrol - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestAPIV1AuthMiddleware proves the v1 API, mounted on the real router, is -// guarded by the Huma security middleware: missing or bad Bearer tokens are -// rejected with 401 before reaching a handler; a valid key passes to 200. The -// per-endpoint harness bypasses auth via WithLocalTrust, so this is the one -// place the middleware itself is exercised. -func TestAPIV1AuthMiddleware(t *testing.T) { - app := createTestApp(t) - handler := app.HTTPHandler() - - expiry := time.Now().Add(time.Hour) - valid, _, err := app.state.CreateAPIKey(&expiry) - require.NoError(t, err) - - tests := []struct { - name string - authHeader string - wantStatus int - }{ - {name: "missing bearer", authHeader: "", wantStatus: http.StatusUnauthorized}, - {name: "no bearer prefix", authHeader: "tskey-invalid", wantStatus: http.StatusUnauthorized}, - {name: "invalid bearer token", authHeader: "Bearer tskey-invalid", wantStatus: http.StatusUnauthorized}, - {name: "valid api key", authHeader: "Bearer " + valid, wantStatus: http.StatusOK}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := httptest.NewRequestWithContext( - context.Background(), http.MethodGet, "/api/v1/node", nil, - ) - if tt.authHeader != "" { - req.Header.Set("Authorization", tt.authHeader) - } - - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - assert.Equalf(t, tt.wantStatus, rec.Code, "body: %s", rec.Body.String()) - }) - } -} - -// TestAPIV1DocsArePublic proves the OpenAPI document and docs UI live under -// /api/v1 and are reachable without an API key, while the operations beside them -// stay key-gated. The docs page points at the versioned spec so a future -// /api/v2 can carry its own. -func TestAPIV1DocsArePublic(t *testing.T) { - app := createTestApp(t) - handler := app.HTTPHandler() - - tests := []struct { - path string - contains string - }{ - {path: "/api/v1/openapi.yaml", contains: "openapi:"}, - {path: "/api/v1/docs", contains: "/api/v1/openapi.yaml"}, - } - - for _, tt := range tests { - t.Run(tt.path, func(t *testing.T) { - // No Authorization header: these must be public. - req := httptest.NewRequestWithContext( - context.Background(), http.MethodGet, tt.path, nil, - ) - - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - require.Equalf(t, http.StatusOK, rec.Code, "%s should be public; body: %s", tt.path, rec.Body.String()) - assert.Containsf(t, rec.Body.String(), tt.contains, "%s body", tt.path) - }) - } -} - -// TestAPIV1Unauthorized401 pins the 401 body: RFC 7807 JSON containing -// "Unauthorized", under 100 bytes and leaking no data, as the integration -// auth-bypass tests require. -func TestAPIV1Unauthorized401(t *testing.T) { - app := createTestApp(t) - handler := app.HTTPHandler() - - req := httptest.NewRequestWithContext( - context.Background(), http.MethodGet, "/api/v1/node", nil, - ) - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - require.Equal(t, http.StatusUnauthorized, rec.Code) - - body := rec.Body.Bytes() - assert.Contains(t, string(body), "Unauthorized") - assert.Lessf(t, len(body), 100, "401 body must stay small (no data leak): %s", body) - - var problem map[string]any - require.NoError(t, json.Unmarshal(body, &problem), "401 body must be JSON: %s", body) - assert.NotContains(t, problem, "users") -} diff --git a/hscontrol/apiv1_client_test.go b/hscontrol/apiv1_client_test.go deleted file mode 100644 index 536d6177d..000000000 --- a/hscontrol/apiv1_client_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package hscontrol - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - clientv1 "github.com/juanfont/headscale/gen/client/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestAPIV1GeneratedClient smoke-tests the generated client against the Huma -// service over a real HTTP server, exercising the typed request/response path -// end to end. -func TestAPIV1GeneratedClient(t *testing.T) { - app := createTestApp(t) - srv := httptest.NewServer(newHumaTestHandler(app)) - t.Cleanup(srv.Close) - - client, err := clientv1.NewClientWithResponses(srv.URL) - require.NoError(t, err) - - ctx := context.Background() - - health, err := client.HealthWithResponse(ctx) - require.NoError(t, err) - require.Equal(t, http.StatusOK, health.StatusCode()) - require.NotNil(t, health.JSON200) - assert.True(t, health.JSON200.DatabaseConnectivity) - - name := "alice" - created, err := client.CreateUserWithResponse(ctx, clientv1.CreateUserJSONRequestBody{ - Name: &name, - }) - require.NoError(t, err) - require.Equal(t, http.StatusOK, created.StatusCode()) - require.NotNil(t, created.JSON200) - require.NotNil(t, created.JSON200.User) - assert.Equal(t, "alice", created.JSON200.User.Name) - assert.Equal(t, "1", created.JSON200.User.Id) - - listed, err := client.ListUsersWithResponse(ctx, &clientv1.ListUsersParams{}) - require.NoError(t, err) - require.Equal(t, http.StatusOK, listed.StatusCode()) - require.NotNil(t, listed.JSON200) - require.Len(t, listed.JSON200.Users, 1) - assert.Equal(t, "alice", listed.JSON200.Users[0].Name) -} diff --git a/hscontrol/apiv1_harness_test.go b/hscontrol/apiv1_harness_test.go deleted file mode 100644 index 256ccf89b..000000000 --- a/hscontrol/apiv1_harness_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package hscontrol - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - apiv1 "github.com/juanfont/headscale/hscontrol/api/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// apiV1Harness drives a request through the Huma service and compares the result -// against a golden keyed by the test name. Goldens neutralise timestamps so they -// stay stable across runs; refresh with HEADSCALE_UPDATE_GOLDEN=1 after an -// intentional contract change. assertParity suits reads/errors; mutations use -// assertParityIsolated against a freshly-seeded app. -type apiV1Harness struct { - app *Headscale - huma http.Handler -} - -func newAPIV1Harness(t *testing.T) *apiV1Harness { - t.Helper() - - app := createTestApp(t) - - return &apiV1Harness{ - app: app, - huma: newHumaTestHandler(app), - } -} - -// newHumaTestHandler wraps the Huma handler in WithLocalTrust to bypass the -// bearer-key middleware — the same local-trust the unix socket gets — so these -// tests can exercise response shapes. Auth itself is covered against the full -// router in TestAPIV1AuthMiddleware. -func newHumaTestHandler(app *Headscale) http.Handler { - mux, _ := apiv1.Handler(apiv1.Backend{ - State: app.state, - Change: app.Change, - Cfg: app.cfg, - }) - - return apiv1.WithLocalTrust(mux) -} - -// httpResult is one HTTP exchange captured for comparison. -type httpResult struct { - status int - body []byte -} - -func callHandler(handler http.Handler, method, path string, body []byte) httpResult { - var reader io.Reader - if body != nil { - reader = bytes.NewReader(body) - } - - req := httptest.NewRequestWithContext(context.Background(), method, path, reader) - if body != nil { - req.Header.Set("Content-Type", "application/json") - } - - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - return httpResult{status: rec.Code, body: rec.Body.Bytes()} -} - -func (h *apiV1Harness) callHuma(method, path string, body []byte) httpResult { - return callHandler(h.huma, method, path, body) -} - -// assertParity asserts the Huma response matches the golden: equal status, and -// for success an equal JSON body (timestamps neutralised). Error bodies are not -// compared — the shape deliberately deviates from legacy -// ({code,message,details} vs Huma RFC7807). -func (h *apiV1Harness) assertParity(t *testing.T, method, path string, body []byte) httpResult { - t.Helper() - - hum := h.callHuma(method, path, body) - - assertAgainstGolden(t, method, path, hum) - - return hum -} - -// assertParityIsolated runs the request against a fresh app seeded by seed (may -// be nil) and compares to the golden. Use for mutations. -func assertParityIsolated( - t *testing.T, - seed func(t *testing.T, app *Headscale), - method, path string, - body []byte, -) httpResult { - t.Helper() - - humaApp := createTestApp(t) - if seed != nil { - seed(t, humaApp) - } - - hum := callHandler(newHumaTestHandler(humaApp), method, path, body) - - assertAgainstGolden(t, method, path, hum) - - return hum -} - -// assertStatus pins the exact HTTP status independently of the golden, so a -// regression fails even if the golden is blindly regenerated. -func assertStatus(t *testing.T, got httpResult, want int) { - t.Helper() - - assert.Equalf(t, want, got.status, - "unexpected HTTP status (golden-independent guard); body: %s", got.body) -} - -func isSuccess(status int) bool { - return status >= 200 && status < 300 -} - -const goldenDir = "testdata/apiv1_golden" - -// goldenRecord is the persisted shape of a captured response: the HTTP status -// and, when present, the normalised JSON body. -type goldenRecord struct { - Status int `json:"status"` - Body json.RawMessage `json:"body"` -} - -func goldenPath(t *testing.T) string { - t.Helper() - - name := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name()) - - return filepath.Join(goldenDir, name+".json") -} - -// assertAgainstGolden compares the Huma response to the recorded golden: status -// always, body only for success responses. -func assertAgainstGolden(t *testing.T, method, path string, hum httpResult) { - t.Helper() - - gpath := goldenPath(t) - - if os.Getenv("HEADSCALE_UPDATE_GOLDEN") != "" { - writeGolden(t, gpath, hum) - - return - } - - raw, err := os.ReadFile(gpath) - require.NoErrorf(t, err, - "missing golden %s for %s %s — run with HEADSCALE_UPDATE_GOLDEN=1 to generate", - gpath, method, path) - - var golden goldenRecord - require.NoErrorf(t, json.Unmarshal(raw, &golden), "decoding golden %s", gpath) - - assert.Equalf(t, golden.Status, hum.status, - "status mismatch for %s %s\nhuma body: %s", method, path, hum.body) - - if isSuccess(golden.Status) { - var goldenBody any - if len(golden.Body) > 0 { - goldenBody = normalizeJSON(t, golden.Body, true) - } - - assert.Equalf(t, goldenBody, normalizeJSON(t, hum.body, true), - "body mismatch for %s %s", method, path) - } -} - -// writeGolden persists the response under HEADSCALE_UPDATE_GOLDEN. Success -// bodies are normalised the same way the comparison path does, so the file stays -// stable across runs. Error responses are stored status-only, since the reader -// never compares error bodies. -func writeGolden(t *testing.T, gpath string, hum httpResult) { - t.Helper() - - rec := goldenRecord{Status: hum.status} - - if isSuccess(hum.status) { - if normalised := normalizeJSON(t, hum.body, true); normalised != nil { - raw, err := json.Marshal(normalised) - require.NoErrorf(t, err, "marshalling golden body for %s", gpath) - - rec.Body = raw - } - } - - out, err := json.MarshalIndent(rec, "", " ") - require.NoErrorf(t, err, "marshalling golden record for %s", gpath) - - require.NoErrorf(t, os.MkdirAll(filepath.Dir(gpath), 0o755), - "creating golden dir for %s", gpath) - require.NoErrorf(t, os.WriteFile(gpath, append(out, '\n'), 0o600), - "writing golden %s", gpath) - - t.Logf("updated golden %s", gpath) -} - -// nonDeterministicRe matches values that embed per-app random material and so -// cannot match byte-for-byte across apps: masked key/secret prefixes -// (hskey-auth--***, hskey-api--***) and Tailscale key material -// (mkey:/nodekey:/discokey:). Neutralised to a sentinel when neutralise is -// true. -var nonDeterministicRe = regexp.MustCompile( - `^(hskey-(auth|api)-[0-9a-f]+-\*\*\*|(mkey|nodekey|discokey):[0-9a-f]+)$`, -) - -// normalizeJSON decodes JSON for order-independent, type-aware comparison. -// Numbers decode as json.Number so "5" and 5 are not conflated, surfacing -// string-encoding differences. With neutralise, timestamps and per-app random -// values (see nonDeterministicRe) become sentinels; otherwise timestamps are -// canonicalised to a UTC instant. -func normalizeJSON(t *testing.T, b []byte, neutralise bool) any { - t.Helper() - - if len(bytes.TrimSpace(b)) == 0 { - return nil - } - - dec := json.NewDecoder(bytes.NewReader(b)) - dec.UseNumber() - - var v any - require.NoErrorf(t, dec.Decode(&v), "decoding JSON: %s", b) - - return canonicalizeTimestamps(v, neutralise) -} - -func canonicalizeTimestamps(v any, neutralise bool) any { - switch val := v.(type) { - case map[string]any: - for k, child := range val { - val[k] = canonicalizeTimestamps(child, neutralise) - } - - return val - case []any: - for i, child := range val { - val[i] = canonicalizeTimestamps(child, neutralise) - } - - return val - case string: - if neutralise && nonDeterministicRe.MatchString(val) { - return "" - } - - ts, err := time.Parse(time.RFC3339Nano, val) - if err != nil { - return val - } - - if neutralise { - return "" - } - - return ts.UTC().Format(time.RFC3339Nano) - default: - return v - } -} diff --git a/hscontrol/apiv1_health_test.go b/hscontrol/apiv1_health_test.go deleted file mode 100644 index 6e25ddb24..000000000 --- a/hscontrol/apiv1_health_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package hscontrol - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAPIV1Health(t *testing.T) { - h := newAPIV1Harness(t) - - t.Run("huma returns healthy with database connectivity", func(t *testing.T) { - res := h.callHuma(http.MethodGet, "/api/v1/health", nil) - - assert.Equal(t, http.StatusOK, res.status) - require.JSONEq(t, `{"databaseConnectivity":true}`, string(res.body)) - }) - - t.Run("parity with gateway", func(t *testing.T) { - h.assertParity(t, http.MethodGet, "/api/v1/health", nil) - }) -} diff --git a/hscontrol/apiv1_nodes_test.go b/hscontrol/apiv1_nodes_test.go deleted file mode 100644 index dc6e22228..000000000 --- a/hscontrol/apiv1_nodes_test.go +++ /dev/null @@ -1,416 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" - "tailscale.com/types/key" -) - -// nodeSeed carries fixed key material so isolated apps in a mutation parity test -// register byte-identical nodes; otherwise random keys would defeat body -// comparison. -type nodeSeed struct { - user string - machineKey key.MachinePrivate - nodeKey key.NodePrivate - hostname string - tags []string -} - -func newNodeSeed(user, hostname string, tags ...string) nodeSeed { - return nodeSeed{ - user: user, - machineKey: key.NewMachine(), - nodeKey: key.NewNode(), - hostname: hostname, - tags: tags, - } -} - -// register inserts the user, a matching pre-auth key, and the node itself using -// the seed's fixed keys, returning the created node ID. -func (s nodeSeed) register(t *testing.T, app *Headscale) types.NodeID { - t.Helper() - - user := app.state.CreateUserForTest(s.user) - - pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, s.tags) - require.NoError(t, err) - - req := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: s.nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: s.hostname}, - } - - _, err = app.handleRegisterWithAuthKey(req, s.machineKey.Public()) - require.NoError(t, err) - - node, found := app.state.GetNodeByNodeKey(s.nodeKey.Public()) - require.True(t, found) - - return node.ID() -} - -func seedNodes(seeds ...nodeSeed) func(t *testing.T, app *Headscale) { - return func(t *testing.T, app *Headscale) { - t.Helper() - - for _, s := range seeds { - s.register(t, app) - } - } -} - -func TestAPIV1NodeGet(t *testing.T) { - t.Run("happy parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/node/1", nil) - }) - - t.Run("tagged node parity", func(t *testing.T) { - h := newAPIV1Harness(t) - // No tagOwners means policy won't authorise a tag, so register via the - // pre-auth key path, which forces tags regardless of policy. - seedNodes(newNodeSeed("bob", "node-b", "tag:initial"))(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/node/1", nil) - }) - - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodGet, "/api/v1/node/99999", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodGet, "/api/v1/node/abc", nil) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("carol", "node-c"))(t, h.app) - - res := h.callHuma(http.MethodGet, "/api/v1/node/1", nil) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.Node["id"]) - assert.Equal(t, "node-c", got.Node["name"]) - assert.Equal(t, "REGISTER_METHOD_AUTH_KEY", got.Node["registerMethod"]) - // EmitUnpopulated parity: slices present as [], expiry as null. - assert.Equal(t, []any{}, got.Node["ipAddresses"]) - assert.Equal(t, []any{}, got.Node["tags"]) - assert.Nil(t, got.Node["expiry"]) - assert.Contains(t, got.Node, "user") - assert.Contains(t, got.Node, "preAuthKey") - assert.Contains(t, got.Node, "createdAt") - }) -} - -func TestAPIV1NodeList(t *testing.T) { - t.Run("empty returns empty array", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodGet, "/api/v1/node", nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{"nodes":[]}`, string(res.body)) - }) - - t.Run("empty parity", func(t *testing.T) { - h := newAPIV1Harness(t) - h.assertParity(t, http.MethodGet, "/api/v1/node", nil) - }) - - t.Run("all parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes( - newNodeSeed("alice", "node-a"), - newNodeSeed("bob", "node-b"), - )(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/node", nil) - }) - - t.Run("filter by user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes( - newNodeSeed("alice", "node-a"), - newNodeSeed("bob", "node-b"), - )(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/node?user=alice", nil) - }) - - t.Run("unknown user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - res := h.assertParity(t, http.MethodGet, "/api/v1/node?user=nope", nil) - assertStatus(t, res, http.StatusNotFound) - }) -} - -func TestAPIV1NodeDelete(t *testing.T) { - t.Run("happy parity", func(t *testing.T) { - assertParityIsolated(t, seedNodes(newNodeSeed("alice", "node-a")), - http.MethodDelete, "/api/v1/node/1", nil) - }) - - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/node/99999", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/node/abc", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeExpire(t *testing.T) { - // The embedded pre-auth key's masked prefix is random per app, so isolated - // body comparison is impossible. GetNode/ListNodes prove full serialisation; - // here we just assert the mutation took effect. - t.Run("huma expires node", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - - res := h.callHuma(http.MethodPost, "/api/v1/node/1/expire", nil) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.Node["id"]) - // Expiry was nil before; expiring sets it to a concrete timestamp. - assert.NotNil(t, got.Node["expiry"]) - }) - - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/99999/expire", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/abc/expire", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeRename(t *testing.T) { - t.Run("huma renames node", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - - res := h.callHuma(http.MethodPost, "/api/v1/node/1/rename/renamed-node", nil) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "renamed-node", got.Node["givenName"]) - }) - - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/99999/rename/whatever", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/abc/rename/whatever", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeSetTags(t *testing.T) { - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/99999/tags", - []byte(`{"tags":["tag:foo"]}`)) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("empty tags parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/1/tags", []byte(`{"tags":[]}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("unauthorized tag parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - // No tagOwners in policy → SetNodeTags rejects with InvalidArgument (400). - res := h.assertParity(t, http.MethodPost, "/api/v1/node/1/tags", - []byte(`{"tags":["tag:server"]}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/abc/tags", - []byte(`{"tags":["tag:foo"]}`)) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeSetApprovedRoutes(t *testing.T) { - t.Run("huma sets approved routes", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - - res := h.callHuma(http.MethodPost, "/api/v1/node/1/approve_routes", - []byte(`{"routes":[]}`)) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.Node["id"]) - // SubnetRoutes is recomputed from primary routes; empty here but present. - assert.Equal(t, []any{}, got.Node["approvedRoutes"]) - assert.Equal(t, []any{}, got.Node["subnetRoutes"]) - }) - - t.Run("not found parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/99999/approve_routes", - []byte(`{"routes":["10.0.0.0/24"]}`)) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/abc/approve_routes", - []byte(`{"routes":[]}`)) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeRegister(t *testing.T) { - t.Run("happy parity", func(t *testing.T) { - authID := types.MustAuthID() - mk := key.NewMachine() - nk := key.NewNode() - - seed := func(t *testing.T, app *Headscale) { - t.Helper() - - app.state.CreateUserForTest("alice") - - regData := &types.RegistrationData{ - NodeKey: nk.Public(), - MachineKey: mk.Public(), - Hostname: "registered-node", - } - app.state.SetAuthCacheEntry(authID, types.NewRegisterAuthRequest(regData)) - } - - assertParityIsolated(t, seed, http.MethodPost, - "/api/v1/node/register?user=alice&key="+authID.String(), nil) - }) - - t.Run("invalid key parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - res := h.assertParity(t, http.MethodPost, - "/api/v1/node/register?user=alice&key=invalidkey", nil) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("unknown user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, - "/api/v1/node/register?user=nope&key="+types.MustAuthID().String(), nil) - assertStatus(t, res, http.StatusNotFound) - }) -} - -func TestAPIV1NodeBackfillIPs(t *testing.T) { - t.Run("confirmed empty parity", func(t *testing.T) { - assertParityIsolated(t, nil, http.MethodPost, - "/api/v1/node/backfillips?confirmed=true", nil) - }) - - t.Run("confirmed returns empty array", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodPost, "/api/v1/node/backfillips?confirmed=true", nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{"changes":[]}`, string(res.body)) - }) - - t.Run("unconfirmed parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/node/backfillips", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1NodeDebugCreate(t *testing.T) { - t.Run("unknown user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - body := []byte(`{"user":"nope","key":"` + types.MustAuthID().String() + - `","name":"dbg","routes":[]}`) - res := h.assertParity(t, http.MethodPost, "/api/v1/debug/node", body) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid key parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedNodes(newNodeSeed("alice", "node-a"))(t, h.app) - - body := []byte(`{"user":"alice","key":"badkey","name":"dbg","routes":[]}`) - res := h.assertParity(t, http.MethodPost, "/api/v1/debug/node", body) - assertStatus(t, res, http.StatusBadRequest) - }) - - // The handler mints fresh key material per call, so isolated apps can't - // produce byte-identical bodies; assert the shape directly instead. - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - h.app.state.CreateUserForTest("alice") - - body := []byte(`{"user":"alice","key":"` + types.MustAuthID().String() + - `","name":"dbgnode","routes":["10.0.0.0/24"]}`) - - res := h.callHuma(http.MethodPost, "/api/v1/debug/node", body) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Node map[string]any `json:"node"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "dbgnode", got.Node["name"]) - assert.Equal(t, "REGISTER_METHOD_UNSPECIFIED", got.Node["registerMethod"]) - assert.Equal(t, []any{"10.0.0.0/24"}, got.Node["availableRoutes"]) - // The synthetic echo node has no pre-auth key: emitted as null. - assert.Nil(t, got.Node["preAuthKey"]) - // Zero-time expiry/lastSeen are emitted as the zero instant, not null. - assert.Equal(t, "0001-01-01T00:00:00Z", got.Node["expiry"]) - assert.Equal(t, "0001-01-01T00:00:00Z", got.Node["lastSeen"]) - }) -} diff --git a/hscontrol/apiv1_policy_test.go b/hscontrol/apiv1_policy_test.go deleted file mode 100644 index 0c4cfefa0..000000000 --- a/hscontrol/apiv1_policy_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/mapper" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// validPolicy is a minimal policy that passes validation without nodes or users. -const validPolicy = `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - -// invalidPolicy is not valid HuJSON, so policy manager construction fails. -const invalidPolicy = `{"acls": [` - -// seedPolicy stores a policy in the database so reads return it. -func seedPolicy(policy string) func(t *testing.T, app *Headscale) { - return func(t *testing.T, app *Headscale) { - t.Helper() - - _, err := app.state.SetPolicyInDB(policy) - require.NoError(t, err) - } -} - -func TestAPIV1PolicyGet(t *testing.T) { - t.Run("empty parity", func(t *testing.T) { - // With no policy stored, the DB load fails; this is treated as a server - // fault (500), matching the legacy contract. - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodGet, "/api/v1/policy", nil) - assertStatus(t, res, http.StatusInternalServerError) - }) - - t.Run("set parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedPolicy(validPolicy)(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/policy", nil) - }) - - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - seedPolicy(validPolicy)(t, h.app) - - res := h.callHuma(http.MethodGet, "/api/v1/policy", nil) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Policy string `json:"policy"` - UpdatedAt string `json:"updatedAt"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.JSONEq(t, validPolicy, got.Policy) - // updatedAt is emitted even though no omitempty is set. - assert.NotEmpty(t, got.UpdatedAt) - }) -} - -func TestAPIV1PolicySet(t *testing.T) { - t.Run("valid parity", func(t *testing.T) { - body, err := json.Marshal(map[string]string{"policy": validPolicy}) - require.NoError(t, err) - - assertParityIsolated(t, nil, http.MethodPut, "/api/v1/policy", body) - }) - - t.Run("invalid parity", func(t *testing.T) { - body, err := json.Marshal(map[string]string{"policy": invalidPolicy}) - require.NoError(t, err) - - res := assertParityIsolated(t, nil, http.MethodPut, "/api/v1/policy", body) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - - body, err := json.Marshal(map[string]string{"policy": validPolicy}) - require.NoError(t, err) - - res := h.callHuma(http.MethodPut, "/api/v1/policy", body) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - Policy string `json:"policy"` - UpdatedAt string `json:"updatedAt"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.JSONEq(t, validPolicy, got.Policy) - assert.NotEmpty(t, got.UpdatedAt) - }) - - t.Run("not-db mode rejected", func(t *testing.T) { - // Policy updates are only valid in DB mode; file mode rejects with 400. - // createTestApp is DB mode, so build a file-mode app here. - humaApp := createFilePolicyApp(t) - - body, err := json.Marshal(map[string]string{"policy": validPolicy}) - require.NoError(t, err) - - hum := callHandler(newHumaTestHandler(humaApp), http.MethodPut, "/api/v1/policy", body) - - assert.Equal(t, http.StatusBadRequest, hum.status, - "huma body: %s", hum.body) - }) -} - -func TestAPIV1PolicyCheck(t *testing.T) { - t.Run("valid parity", func(t *testing.T) { - h := newAPIV1Harness(t) - - body, err := json.Marshal(map[string]string{"policy": validPolicy}) - require.NoError(t, err) - - h.assertParity(t, http.MethodPost, "/api/v1/policy/check", body) - }) - - t.Run("invalid parity", func(t *testing.T) { - h := newAPIV1Harness(t) - - body, err := json.Marshal(map[string]string{"policy": invalidPolicy}) - require.NoError(t, err) - - res := h.assertParity(t, http.MethodPost, "/api/v1/policy/check", body) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("valid returns empty object", func(t *testing.T) { - h := newAPIV1Harness(t) - - body, err := json.Marshal(map[string]string{"policy": validPolicy}) - require.NoError(t, err) - - res := h.callHuma(http.MethodPost, "/api/v1/policy/check", body) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{}`, string(res.body)) - }) -} - -// createFilePolicyApp mirrors createTestApp but with file-based policy mode so -// the policy-update-disabled path can be exercised. -func createFilePolicyApp(t *testing.T) *Headscale { - t.Helper() - - tmpDir := t.TempDir() - - cfg := types.Config{ - ServerURL: "http://localhost:8080", - NoisePrivateKeyPath: tmpDir + "/noise_private.key", - Database: types.DatabaseConfig{ - Type: "sqlite3", - Sqlite: types.SqliteConfig{ - Path: tmpDir + "/headscale_test.db", - }, - }, - OIDC: types.OIDCConfig{}, - Policy: types.PolicyConfig{ - Mode: types.PolicyModeFile, - }, - Tuning: types.Tuning{ - BatchChangeDelay: 100 * time.Millisecond, - BatcherWorkers: 1, - }, - } - - app, err := NewHeadscale(&cfg) - require.NoError(t, err) - - app.mapBatcher = mapper.NewBatcherAndMapper(&cfg, app.state) - app.mapBatcher.Start() - - t.Cleanup(func() { - if app.mapBatcher != nil { - app.mapBatcher.Close() - } - }) - - return app -} diff --git a/hscontrol/apiv1_preauthkeys_test.go b/hscontrol/apiv1_preauthkeys_test.go deleted file mode 100644 index 2df5f8e6d..000000000 --- a/hscontrol/apiv1_preauthkeys_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// seedPreAuthKeys creates a user-owned key and a tagged (system-created) key; -// the tagged key exercises the user:null path. -func seedPreAuthKeys() func(t *testing.T, app *Headscale) { - return func(t *testing.T, app *Headscale) { - t.Helper() - - user := app.state.CreateUserForTest("alice") - uid := types.UserID(user.ID) - exp := time.Time{} - - _, err := app.state.CreatePreAuthKey(&uid, true, false, &exp, nil) - require.NoError(t, err) - - // Tagged, system-created: no user. - _, err = app.state.CreatePreAuthKey(nil, false, true, &exp, []string{"tag:test"}) - require.NoError(t, err) - } -} - -func TestAPIV1CreatePreAuthKey(t *testing.T) { - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - h.app.state.CreateUserForTest("alice") - - res := h.callHuma(http.MethodPost, "/api/v1/preauthkey", - []byte(`{"user":"1","reusable":true}`)) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - PreAuthKey map[string]any `json:"preAuthKey"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.PreAuthKey["id"]) - assert.Equal(t, true, got.PreAuthKey["reusable"]) - // Zero-value fields are emitted (EmitUnpopulated parity). - assert.Equal(t, false, got.PreAuthKey["ephemeral"]) - assert.Equal(t, false, got.PreAuthKey["used"]) - assert.Contains(t, got.PreAuthKey, "expiration") - assert.Contains(t, got.PreAuthKey, "createdAt") - // Empty tags serialize as [], not null. - assert.Equal(t, []any{}, got.PreAuthKey["aclTags"]) - // The freshly created key returns its full secret. - assert.NotEmpty(t, got.PreAuthKey["key"]) - user, ok := got.PreAuthKey["user"].(map[string]any) - require.True(t, ok) - assert.Equal(t, "1", user["id"]) - }) - - t.Run("tagged key has null user", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodPost, "/api/v1/preauthkey", - []byte(`{"aclTags":["tag:test"]}`)) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - PreAuthKey map[string]any `json:"preAuthKey"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Nil(t, got.PreAuthKey["user"]) - assert.Equal(t, []any{"tag:test"}, got.PreAuthKey["aclTags"]) - }) - - t.Run("invalid tag parity", func(t *testing.T) { - h := newAPIV1Harness(t) - h.app.state.CreateUserForTest("alice") - res := h.assertParity(t, http.MethodPost, "/api/v1/preauthkey", - []byte(`{"user":"1","aclTags":["badtag"]}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("nonexistent user parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/preauthkey", []byte(`{"user":"999"}`)) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid user id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/preauthkey", []byte(`{"user":"abc"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) - - t.Run("neither tagged nor owned parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/preauthkey", []byte(`{"reusable":true}`)) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1ExpirePreAuthKey(t *testing.T) { - t.Run("parity", func(t *testing.T) { - assertParityIsolated(t, seedPreAuthKeys(), http.MethodPost, - "/api/v1/preauthkey/expire", []byte(`{"id":"1"}`)) - }) - - t.Run("nonexistent parity", func(t *testing.T) { - res := assertParityIsolated(t, seedPreAuthKeys(), http.MethodPost, - "/api/v1/preauthkey/expire", []byte(`{"id":"99999"}`)) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/preauthkey/expire", []byte(`{"id":"abc"}`)) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1DeletePreAuthKey(t *testing.T) { - t.Run("parity", func(t *testing.T) { - assertParityIsolated(t, seedPreAuthKeys(), http.MethodDelete, - "/api/v1/preauthkey?id=1", nil) - }) - - t.Run("nonexistent parity", func(t *testing.T) { - res := assertParityIsolated(t, seedPreAuthKeys(), http.MethodDelete, - "/api/v1/preauthkey?id=99999", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/preauthkey?id=abc", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1ListPreAuthKeys(t *testing.T) { - t.Run("empty returns empty array", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodGet, "/api/v1/preauthkey", nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{"preAuthKeys":[]}`, string(res.body)) - }) - - t.Run("empty parity", func(t *testing.T) { - h := newAPIV1Harness(t) - h.assertParity(t, http.MethodGet, "/api/v1/preauthkey", nil) - }) - - t.Run("all parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedPreAuthKeys()(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/preauthkey", nil) - }) -} diff --git a/hscontrol/apiv1_socket_test.go b/hscontrol/apiv1_socket_test.go deleted file mode 100644 index 0b6f53ec3..000000000 --- a/hscontrol/apiv1_socket_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package hscontrol - -import ( - "context" - "net" - "net/http" - "path/filepath" - "testing" - - clientv1 "github.com/juanfont/headscale/gen/client/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestAPIV1SocketClient proves the CLI's local transport: the Huma handler over -// a unix socket reached by an http.Client that dials it, the same wiring as the -// server's socket listener and the CLI's newSocketClient, on local trust. -func TestAPIV1SocketClient(t *testing.T) { - app := createTestApp(t) - - socketPath := filepath.Join(t.TempDir(), "headscale.sock") - - lis, err := new(net.ListenConfig).Listen(context.Background(), "unix", socketPath) - require.NoError(t, err) - - srv := &http.Server{Handler: newHumaTestHandler(app)} //nolint:gosec - go func() { _ = srv.Serve(lis) }() - - t.Cleanup(func() { _ = srv.Close() }) - - httpClient := &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - var d net.Dialer - - return d.DialContext(ctx, "unix", socketPath) - }, - }, - } - - client, err := clientv1.NewClientWithResponses( - "http://local", - clientv1.WithHTTPClient(httpClient), - ) - require.NoError(t, err) - - ctx := context.Background() - - health, err := client.HealthWithResponse(ctx) - require.NoError(t, err) - require.Equal(t, http.StatusOK, health.StatusCode()) - require.NotNil(t, health.JSON200) - assert.True(t, health.JSON200.DatabaseConnectivity) - - name := "socket-user" - created, err := client.CreateUserWithResponse(ctx, clientv1.CreateUserJSONRequestBody{Name: &name}) - require.NoError(t, err) - require.Equal(t, http.StatusOK, created.StatusCode()) - require.NotNil(t, created.JSON200) - assert.Equal(t, "socket-user", created.JSON200.User.Name) -} diff --git a/hscontrol/apiv1_users_test.go b/hscontrol/apiv1_users_test.go deleted file mode 100644 index 428ba21c6..000000000 --- a/hscontrol/apiv1_users_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func seedUsers(names ...string) func(t *testing.T, app *Headscale) { - return func(t *testing.T, app *Headscale) { - t.Helper() - - for _, n := range names { - app.state.CreateUserForTest(n) - } - } -} - -func TestAPIV1CreateUser(t *testing.T) { - t.Run("huma response shape", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodPost, "/api/v1/user", []byte(`{"name":"test"}`)) - require.Equal(t, http.StatusOK, res.status) - - var got struct { - User map[string]any `json:"user"` - } - require.NoError(t, json.Unmarshal(res.body, &got)) - - assert.Equal(t, "1", got.User["id"]) - assert.Equal(t, "test", got.User["name"]) - assert.Contains(t, got.User, "createdAt") - // Zero-value fields are emitted as empty strings (EmitUnpopulated parity). - assert.Empty(t, got.User["email"]) - assert.Empty(t, got.User["displayName"]) - }) - - t.Run("parity", func(t *testing.T) { - assertParityIsolated(t, nil, http.MethodPost, "/api/v1/user", - []byte(`{"name":"test","displayName":"Test","email":"t@example.com"}`)) - }) - - t.Run("duplicate name parity", func(t *testing.T) { - res := assertParityIsolated(t, seedUsers("dup"), http.MethodPost, "/api/v1/user", - []byte(`{"name":"dup"}`)) - assertStatus(t, res, http.StatusConflict) - }) -} - -func TestAPIV1RenameUser(t *testing.T) { - t.Run("parity", func(t *testing.T) { - assertParityIsolated(t, seedUsers("alice"), http.MethodPost, - "/api/v1/user/1/rename/bob", nil) - }) - - t.Run("nonexistent parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/user/999/rename/bob", nil) - assertStatus(t, res, http.StatusNotFound) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodPost, "/api/v1/user/abc/rename/bob", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} - -func TestAPIV1DeleteUser(t *testing.T) { - t.Run("parity", func(t *testing.T) { - assertParityIsolated(t, seedUsers("alice"), http.MethodDelete, "/api/v1/user/1", nil) - }) - - t.Run("nonexistent parity", func(t *testing.T) { - h := newAPIV1Harness(t) - res := h.assertParity(t, http.MethodDelete, "/api/v1/user/999", nil) - assertStatus(t, res, http.StatusNotFound) - }) -} - -func TestAPIV1ListUsers(t *testing.T) { - t.Run("empty returns empty array", func(t *testing.T) { - h := newAPIV1Harness(t) - - res := h.callHuma(http.MethodGet, "/api/v1/user", nil) - require.Equal(t, http.StatusOK, res.status) - assert.JSONEq(t, `{"users":[]}`, string(res.body)) - }) - - t.Run("empty parity", func(t *testing.T) { - h := newAPIV1Harness(t) - h.assertParity(t, http.MethodGet, "/api/v1/user", nil) - }) - - t.Run("all parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice", "bob", "carol")(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/user", nil) - }) - - t.Run("filter by name parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice", "bob")(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/user?name=alice", nil) - }) - - t.Run("filter by id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice", "bob")(t, h.app) - h.assertParity(t, http.MethodGet, "/api/v1/user?id=2", nil) - }) - - t.Run("invalid id parity", func(t *testing.T) { - h := newAPIV1Harness(t) - seedUsers("alice")(t, h.app) - res := h.assertParity(t, http.MethodGet, "/api/v1/user?id=abc", nil) - assertStatus(t, res, http.StatusBadRequest) - }) -} diff --git a/hscontrol/apiv2_acl_test.go b/hscontrol/apiv2_acl_test.go deleted file mode 100644 index def9af824..000000000 --- a/hscontrol/apiv2_acl_test.go +++ /dev/null @@ -1,239 +0,0 @@ -package hscontrol - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "errors" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/danielgtaylor/huma/v2/humatest" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const allowAllPolicy = `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - -// storedPolicy returns the HuJSON actually persisted in the DB (server-side -// ground truth), or "" when none is set. -func storedPolicy(t *testing.T, app *Headscale) string { - t.Helper() - - p, err := app.state.GetPolicy() - if errors.Is(err, types.ErrPolicyNotFound) { - return "" - } - - require.NoError(t, err) - - return p.Data -} - -// policyETagOf recomputes the expected ETag independently of the handler, so a -// handler that hashed the wrong buffer is caught. -func policyETagOf(data string) string { - sum := sha256.Sum256([]byte(data)) - - return `"` + hex.EncodeToString(sum[:]) + `"` -} - -// setACL POSTs a raw policy body with the given content type and optional -// headers (e.g. "If-Match: ..."). -func setACL(t *testing.T, api humatest.TestAPI, body, contentType string, headers ...string) *httptest.ResponseRecorder { - t.Helper() - - args := make([]any, 0, 2+len(headers)) - args = append(args, bytes.NewReader([]byte(body)), "Content-Type: "+contentType) - - for _, h := range headers { - args = append(args, h) - } - - return api.Post("/api/v2/tailnet/-/acl", args...) -} - -func TestAPIv2ACLDefaultWhenUnset(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - resp := api.Get("/api/v2/tailnet/-/acl") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - assert.NotEmpty(t, resp.Header().Get("Etag")) - assert.Contains(t, resp.Body.String(), `"acls"`) - - // GET did not materialize a stored policy. - assert.Empty(t, storedPolicy(t, app)) - - // Content-addressed: a second GET returns the same ETag. - assert.Equal(t, resp.Header().Get("Etag"), api.Get("/api/v2/tailnet/-/acl").Header().Get("Etag")) -} - -func TestAPIv2ACLContentNegotiation(t *testing.T) { - api := registerAPIV2(t, createTestApp(t)) - - jsonResp := api.Get("/api/v2/tailnet/-/acl") - huResp := api.Get("/api/v2/tailnet/-/acl", "Accept: application/hujson") - - assert.Equal(t, "application/json", jsonResp.Header().Get("Content-Type")) - assert.Equal(t, "application/hujson", huResp.Header().Get("Content-Type")) - // Same bytes, same ETag — the ETag is over content, not type. - assert.Equal(t, jsonResp.Body.Bytes(), huResp.Body.Bytes()) - assert.Equal(t, jsonResp.Header().Get("Etag"), huResp.Header().Get("Etag")) -} - -func TestAPIv2ACLSetCanonicalJSON(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - set := setACL(t, api, allowAllPolicy, "application/json") - require.Equalf(t, http.StatusOK, set.Code, "body: %s", set.Body) - setETag := set.Header().Get("Etag") - assert.NotEmpty(t, setETag) - - // (a) tool's own get reflects it, same ETag. - get := api.Get("/api/v2/tailnet/-/acl") - assert.Contains(t, get.Body.String(), `"action":"accept"`) - assert.Equal(t, setETag, get.Header().Get("Etag")) - - // (b) server-side: exact stored bytes. - assert.JSONEq(t, allowAllPolicy, storedPolicy(t, app)) - - // (c) ETag is the sha256 of those bytes. - assert.Equal(t, policyETagOf(allowAllPolicy), setETag) -} - -func TestAPIv2ACLSetHuJSONWithComments(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - // Line comment + trailing comma: valid HuJSON, invalid strict JSON. The - // server accepts it (the SDK sends the policy file this way) and standardizes - // it on store; the ACL content survives even though comments are blanked. - policy := "{\n // allow all\n \"acls\": [{\"action\":\"accept\",\"src\":[\"*\"],\"dst\":[\"*:*\"]}],\n}" - - set := setACL(t, api, policy, "application/hujson") - require.Equalf(t, http.StatusOK, set.Code, "body: %s", set.Body) - - // Server-side: stored, parseable, ACL intact. - stored := storedPolicy(t, app) - assert.Contains(t, stored, `"action":"accept"`) - - // GET round-trips the stored form and its content-addressed ETag. - raw := api.Get("/api/v2/tailnet/-/acl", "Accept: application/hujson") - assert.Equal(t, stored, raw.Body.String()) - assert.Equal(t, policyETagOf(stored), raw.Header().Get("Etag")) -} - -func TestAPIv2ACLETagChangesOnChangeStableOnNoop(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - etag1 := setACL(t, api, allowAllPolicy, "application/json").Header().Get("Etag") - stored1 := storedPolicy(t, app) - - p2 := `{"hosts":{"h":"100.64.0.1"},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - etag2 := setACL(t, api, p2, "application/json").Header().Get("Etag") - assert.NotEqual(t, etag1, etag2, "etag changes when policy changes") - assert.NotEqual(t, stored1, storedPolicy(t, app)) - - // No-op re-set keeps the ETag stable. - etag3 := setACL(t, api, p2, "application/json").Header().Get("Etag") - assert.Equal(t, etag2, etag3) - assert.Equal(t, p2, storedPolicy(t, app)) -} - -func TestAPIv2ACLIfMatchPreconditions(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - etag := setACL(t, api, allowAllPolicy, "application/json").Header().Get("Etag") - - p2 := `{"hosts":{"h":"100.64.0.1"},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - - // Match -> 200, applies. - require.Equal(t, http.StatusOK, setACL(t, api, p2, "application/json", "If-Match: "+etag).Code) - assert.Equal(t, p2, storedPolicy(t, app)) - - // Mismatch -> 412, server unchanged. - before := storedPolicy(t, app) - assert.Equal(t, http.StatusPreconditionFailed, - setACL(t, api, allowAllPolicy, "application/json", `If-Match: "deadbeef"`).Code) - assert.Equal(t, before, storedPolicy(t, app), "rejected write left the policy untouched") - - // Absent -> unconditional 200. - require.Equal(t, http.StatusOK, setACL(t, api, allowAllPolicy, "application/json").Code) - assert.JSONEq(t, allowAllPolicy, storedPolicy(t, app)) -} - -func TestAPIv2ACLIfMatchTsDefault(t *testing.T) { - // No policy set: ts-default matches the allow-all default -> 200. - app := createTestApp(t) - api := registerAPIV2(t, app) - require.Equal(t, http.StatusOK, - setACL(t, api, allowAllPolicy, "application/json", `If-Match: "ts-default"`).Code) - assert.JSONEq(t, allowAllPolicy, storedPolicy(t, app)) - - // A non-default policy is set: ts-default no longer matches -> 412. - assert.Equal(t, http.StatusPreconditionFailed, - setACL(t, api, `{"hosts":{"h":"100.64.0.1"},"acls":[]}`, "application/json", `If-Match: "ts-default"`).Code) -} - -func TestAPIv2ACLInvalidPolicyAtomicity(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - good := setACL(t, api, allowAllPolicy, "application/json") - require.Equal(t, http.StatusOK, good.Code) - goodETag := good.Header().Get("Etag") - - // Malformed body -> 400, and the stored policy is unchanged (atomicity). - bad := setACL(t, api, `{ this is not valid`, "application/json") - assert.Equal(t, http.StatusBadRequest, bad.Code) - assert.Contains(t, bad.Body.String(), `"message"`) - assert.JSONEq(t, allowAllPolicy, storedPolicy(t, app)) - - // Wire-level: GET still serves the good policy + its ETag (no drift). - get := api.Get("/api/v2/tailnet/-/acl") - assert.Equal(t, goodETag, get.Header().Get("Etag")) - assert.JSONEq(t, allowAllPolicy, storedPolicy(t, app)) -} - -func TestAPIv2ACLNonDefaultTailnet404(t *testing.T) { - api := registerAPIV2(t, createTestApp(t)) - - assert.Equal(t, http.StatusNotFound, api.Get("/api/v2/tailnet/example.com/acl").Code) - assert.Equal(t, http.StatusNotFound, - api.Post("/api/v2/tailnet/example.com/acl", bytes.NewReader([]byte(allowAllPolicy))).Code) -} - -func TestAPIv2ACLFileModeReadOnly(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "acl.hujson") - fileBytes := "{\n // file-managed\n \"acls\": [{\"action\":\"accept\",\"src\":[\"*\"],\"dst\":[\"*:*\"]}],\n}" - require.NoError(t, os.WriteFile(path, []byte(fileBytes), 0o600)) - - cfg := &types.Config{Policy: types.PolicyConfig{Mode: types.PolicyModeFile, Path: path}} - _, api := humatest.New(t, apiv2.Config()) - apiv2.Register(api, apiv2.Backend{Cfg: cfg}) - - // GET serves the file bytes + their ETag. - get := api.Get("/api/v2/tailnet/-/acl") - require.Equalf(t, http.StatusOK, get.Code, "body: %s", get.Body) - assert.Equal(t, fileBytes, get.Body.String()) - assert.Equal(t, policyETagOf(fileBytes), get.Header().Get("Etag")) - - // POST is rejected in file mode; the file is unchanged. - set := setACL(t, api, allowAllPolicy, "application/json") - assert.Equal(t, http.StatusBadRequest, set.Code) - assert.Contains(t, set.Body.String(), "database") - - after, err := os.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, fileBytes, string(after)) -} diff --git a/hscontrol/apiv2_devices_test.go b/hscontrol/apiv2_devices_test.go deleted file mode 100644 index 24c0d77a4..000000000 --- a/hscontrol/apiv2_devices_test.go +++ /dev/null @@ -1,343 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "strconv" - "testing" - "time" - - "github.com/danielgtaylor/huma/v2/humatest" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// registerAPIV2 builds a humatest API with the v2 operations registered over the -// app's state, with no auth middleware (the contract tests exercise wire shapes; -// auth is covered by TestAPIv2). The full Backend (Change + Cfg) is wired so the -// device/acl write handlers work. -func registerAPIV2(t *testing.T, app *Headscale) humatest.TestAPI { - t.Helper() - - _, api := humatest.New(t, apiv2.Config()) - apiv2.Register(api, apiv2.Backend{State: app.state, Change: app.Change, Cfg: app.cfg}) - - return api -} - -// deviceTestEnv is one seeded, registered, user-owned node plus the v2 API. -type deviceTestEnv struct { - app *Headscale - api humatest.TestAPI - user *types.User - nodeID types.NodeID - deviceID string -} - -// newDeviceTestEnv seeds a registered user-owned node into the NodeStore, with -// tag:ci and tag:prod declared in policy so tagging is permitted. -func newDeviceTestEnv(t *testing.T) deviceTestEnv { - t.Helper() - - app := createTestApp(t) - user := app.state.CreateUserForTest("dut-user") - - _, err := app.state.SetPolicy([]byte( - `{"tagOwners":{"tag:ci":["` + user.Name + `@"],"tag:prod":["` + user.Name + `@"]},` + - `"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}`, - )) - require.NoError(t, err) - - node := app.state.CreateRegisteredNodeForTest(user, "contract-dut") - node.User = user - view := app.state.PutNodeInStoreForTest(*node) - - return deviceTestEnv{ - app: app, - api: registerAPIV2(t, app), - user: user, - nodeID: view.ID(), - deviceID: strconv.FormatUint(uint64(view.ID()), 10), - } -} - -// srvNode reloads the node from the NodeStore — the server-side source of truth. -func (e deviceTestEnv) srvNode(t *testing.T) types.NodeView { - t.Helper() - - v, ok := e.app.state.GetNodeByID(e.nodeID) - require.True(t, ok, "node must exist in the NodeStore") - require.True(t, v.Valid()) - - return v -} - -// seedExpiry stamps a future expiry so set-key(disable) is a real transition. -func (e deviceTestEnv) seedExpiry(t *testing.T, at time.Time) { - t.Helper() - - _, _, err := e.app.state.SetNodeExpiry(e.nodeID, &at) - require.NoError(t, err) -} - -func getDevice(t *testing.T, api humatest.TestAPI, deviceID string) apiv2.Device { - t.Helper() - - resp := api.Get("/api/v2/device/" + deviceID) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var dev apiv2.Device - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &dev)) - - return dev -} - -func getDeviceRoutes(t *testing.T, api humatest.TestAPI, deviceID string) apiv2.DeviceRoutes { - t.Helper() - - resp := api.Get("/api/v2/device/" + deviceID + "/routes") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var routes apiv2.DeviceRoutes - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &routes)) - - return routes -} - -func approvedRouteStrings(nv types.NodeView) []string { - return util.PrefixesToString(nv.ApprovedRoutes().AsSlice()) -} - -func TestAPIv2Device_Get(t *testing.T) { - e := newDeviceTestEnv(t) - - resp := e.api.Get("/api/v2/device/" + e.deviceID + "?fields=all") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var dev apiv2.Device - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &dev)) - assert.Equal(t, e.deviceID, dev.ID) - assert.Equal(t, e.deviceID, dev.NodeID) - assert.Equal(t, "contract-dut", dev.Hostname) - assert.Equal(t, "contract-dut", dev.Name) - assert.True(t, dev.Authorized) - assert.Equal(t, e.user.Username(), dev.User) - assert.NotNil(t, dev.Tags) - assert.True(t, dev.KeyExpiryDisabled, "seeded node has no expiry") - assert.NotEmpty(t, dev.Addresses) - - // Server-side cross-check. - n := e.srvNode(t) - assert.Equal(t, n.GivenName(), dev.Name) - assert.False(t, n.IsTagged()) - assert.True(t, n.User().Valid()) - assert.False(t, n.Expiry().Valid()) - assert.Equal(t, n.IPsAsString(), dev.Addresses) -} - -func TestAPIv2Device_Get_UnknownID_404(t *testing.T) { - e := newDeviceTestEnv(t) - - assert.Equal(t, http.StatusNotFound, e.api.Get("/api/v2/device/999999").Code) - assert.Equal(t, http.StatusNotFound, e.api.Get("/api/v2/device/not-a-number").Code) -} - -func TestAPIv2Device_List(t *testing.T) { - e := newDeviceTestEnv(t) - - list := func() []apiv2.Device { - t.Helper() - - resp := e.api.Get("/api/v2/tailnet/-/devices") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var out struct { - Devices []apiv2.Device `json:"devices"` - } - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) - - return out.Devices - } - - assert.True(t, containsDeviceID(list(), e.deviceID)) - assert.Len(t, list(), e.app.state.ListNodes().Len(), "list count == ListNodes") - - // A second node appears; deleting it removes it from both the list and state. - node2 := e.app.state.CreateRegisteredNodeForTest(e.user, "contract-dut-2") - node2.User = e.user - view2 := e.app.state.PutNodeInStoreForTest(*node2) - id2 := strconv.FormatUint(uint64(view2.ID()), 10) - assert.True(t, containsDeviceID(list(), id2)) - - require.Equal(t, http.StatusOK, e.api.Delete("/api/v2/device/"+id2).Code) - assert.False(t, containsDeviceID(list(), id2)) - - _, ok := e.app.state.GetNodeByID(view2.ID()) - assert.False(t, ok) - - assert.Equal(t, http.StatusNotFound, e.api.Get("/api/v2/tailnet/example.com/devices").Code) -} - -func TestAPIv2Device_SetName(t *testing.T) { - e := newDeviceTestEnv(t) - - require.Equal(t, http.StatusOK, - e.api.Post("/api/v2/device/"+e.deviceID+"/name", map[string]any{"name": "renamed-dut"}).Code) - assert.Equal(t, "renamed-dut", getDevice(t, e.api, e.deviceID).Name) - assert.Equal(t, "renamed-dut", e.srvNode(t).GivenName()) - - // Rename again. - require.Equal(t, http.StatusOK, - e.api.Post("/api/v2/device/"+e.deviceID+"/name", map[string]any{"name": "renamed-again"}).Code) - assert.Equal(t, "renamed-again", getDevice(t, e.api, e.deviceID).Name) - assert.Equal(t, "renamed-again", e.srvNode(t).GivenName()) - - // Invalid DNS label is rejected; the name is unchanged. - bad := e.api.Post("/api/v2/device/"+e.deviceID+"/name", map[string]any{"name": "Invalid_Name!"}) - assert.Equal(t, http.StatusBadRequest, bad.Code) - assert.Contains(t, bad.Body.String(), `"message"`) - assert.Equal(t, "renamed-again", e.srvNode(t).GivenName()) -} - -func TestAPIv2Device_SetTags(t *testing.T) { - e := newDeviceTestEnv(t) - - setTags := func(tags []string) int { - return e.api.Post("/api/v2/device/"+e.deviceID+"/tags", map[string]any{"tags": tags}).Code - } - - // One tag flips the node to tag-owned. - require.Equal(t, http.StatusOK, setTags([]string{"tag:ci"})) - assert.Equal(t, []string{"tag:ci"}, getDevice(t, e.api, e.deviceID).Tags) - n := e.srvNode(t) - assert.True(t, n.IsTagged()) - assert.Equal(t, []string{"tag:ci"}, n.Tags().AsSlice()) - assert.False(t, n.User().Valid()) - assert.Equal(t, types.TaggedDevices.Username(), getDevice(t, e.api, e.deviceID).User) - - // Two tags (sorted). - require.Equal(t, http.StatusOK, setTags([]string{"tag:ci", "tag:prod"})) - assert.Equal(t, []string{"tag:ci", "tag:prod"}, getDevice(t, e.api, e.deviceID).Tags) - assert.Equal(t, []string{"tag:ci", "tag:prod"}, e.srvNode(t).Tags().AsSlice()) - - // A different single tag replaces the set. - require.Equal(t, http.StatusOK, setTags([]string{"tag:prod"})) - assert.Equal(t, []string{"tag:prod"}, e.srvNode(t).Tags().AsSlice()) - - // Remove-all is a no-op: tags survive. - require.Equal(t, http.StatusOK, setTags([]string{})) - assert.Equal(t, []string{"tag:prod"}, e.srvNode(t).Tags().AsSlice()) - assert.True(t, e.srvNode(t).IsTagged()) - - // An undeclared tag is rejected with 400 (mapError now maps the sentinel), - // and the server-side tags are unchanged. - assert.Equal(t, http.StatusBadRequest, setTags([]string{"tag:nope"})) - assert.Equal(t, []string{"tag:prod"}, e.srvNode(t).Tags().AsSlice()) -} - -func TestAPIv2Device_SetKey(t *testing.T) { - e := newDeviceTestEnv(t) - e.seedExpiry(t, time.Now().Add(24*time.Hour)) - - require.True(t, e.srvNode(t).Expiry().Valid(), "precondition: node has an expiry") - assert.False(t, getDevice(t, e.api, e.deviceID).KeyExpiryDisabled) - - require.Equal(t, http.StatusOK, - e.api.Post("/api/v2/device/"+e.deviceID+"/key", map[string]any{"keyExpiryDisabled": true}).Code) - assert.True(t, getDevice(t, e.api, e.deviceID).KeyExpiryDisabled) - assert.False(t, e.srvNode(t).Expiry().Valid(), "expiry cleared") - - // Re-enable is accepted as a no-op; expiry stays cleared. - require.Equal(t, http.StatusOK, - e.api.Post("/api/v2/device/"+e.deviceID+"/key", map[string]any{"keyExpiryDisabled": false}).Code) - assert.True(t, getDevice(t, e.api, e.deviceID).KeyExpiryDisabled) - assert.False(t, e.srvNode(t).Expiry().Valid()) -} - -func TestAPIv2Device_SetRoutes(t *testing.T) { - e := newDeviceTestEnv(t) - - setRoutes := func(routes []string) apiv2.DeviceRoutes { - t.Helper() - - resp := e.api.Post("/api/v2/device/"+e.deviceID+"/routes", map[string]any{"routes": routes}) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var dr apiv2.DeviceRoutes - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &dr)) - - return dr - } - - // One route: enabled reflects it; advertised stays empty (nothing announced). - dr := setRoutes([]string{"10.0.0.0/24"}) - assert.Contains(t, dr.Enabled, "10.0.0.0/24") - assert.Empty(t, dr.Advertised) - assert.Contains(t, getDeviceRoutes(t, e.api, e.deviceID).Enabled, "10.0.0.0/24") - assert.Contains(t, approvedRouteStrings(e.srvNode(t)), "10.0.0.0/24") - assert.Empty(t, e.srvNode(t).AnnouncedRoutes()) - - // Two routes. - setRoutes([]string{"10.0.0.0/24", "192.168.0.0/24"}) - - approved := approvedRouteStrings(e.srvNode(t)) - assert.Contains(t, approved, "10.0.0.0/24") - assert.Contains(t, approved, "192.168.0.0/24") - - // Exit route expands to both families. - setRoutes([]string{"0.0.0.0/0"}) - - approved = approvedRouteStrings(e.srvNode(t)) - assert.Contains(t, approved, "0.0.0.0/0") - assert.Contains(t, approved, "::/0") - - // Clear. - dr = setRoutes([]string{}) - assert.Empty(t, dr.Enabled) - assert.Empty(t, approvedRouteStrings(e.srvNode(t))) - - // Malformed prefix is rejected; routes unchanged. - bad := e.api.Post("/api/v2/device/"+e.deviceID+"/routes", map[string]any{"routes": []string{"not-a-cidr"}}) - assert.Equal(t, http.StatusBadRequest, bad.Code) - assert.Empty(t, approvedRouteStrings(e.srvNode(t))) -} - -func TestAPIv2Device_SetAuthorized(t *testing.T) { - e := newDeviceTestEnv(t) - - require.Equal(t, http.StatusOK, - e.api.Post("/api/v2/device/"+e.deviceID+"/authorized", map[string]any{"authorized": true}).Code) - assert.True(t, getDevice(t, e.api, e.deviceID).Authorized) - - // De-authorize is rejected; the node stays present and authorized. - bad := e.api.Post("/api/v2/device/"+e.deviceID+"/authorized", map[string]any{"authorized": false}) - assert.Equal(t, http.StatusBadRequest, bad.Code) - assert.Contains(t, bad.Body.String(), `"message"`) - assert.True(t, getDevice(t, e.api, e.deviceID).Authorized) -} - -func TestAPIv2Device_Delete(t *testing.T) { - e := newDeviceTestEnv(t) - - require.Equal(t, http.StatusOK, e.api.Delete("/api/v2/device/"+e.deviceID).Code) - assert.Equal(t, http.StatusNotFound, e.api.Get("/api/v2/device/"+e.deviceID).Code) - - _, ok := e.app.state.GetNodeByID(e.nodeID) - assert.False(t, ok, "node gone from the NodeStore") - - // Delete again is 404. - assert.Equal(t, http.StatusNotFound, e.api.Delete("/api/v2/device/"+e.deviceID).Code) -} - -func containsDeviceID(devs []apiv2.Device, id string) bool { - for _, d := range devs { - if d.ID == id || d.NodeID == id { - return true - } - } - - return false -} diff --git a/hscontrol/apiv2_keys_test.go b/hscontrol/apiv2_keys_test.go deleted file mode 100644 index 5a296b2df..000000000 --- a/hscontrol/apiv2_keys_test.go +++ /dev/null @@ -1,322 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "strconv" - "testing" - "time" - - "github.com/danielgtaylor/huma/v2/humatest" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// taggedCaps builds capabilities for a tagged auth key. -func taggedCaps(tags ...string) *apiv2.KeyCapabilities { - return &apiv2.KeyCapabilities{ - Devices: apiv2.KeyDeviceCapabilities{Create: apiv2.KeyDeviceCreateCapabilities{ - Reusable: true, - Preauthorized: true, - Tags: tags, - }}, - } -} - -// newKeyTestAPI builds an app + v2 API with NO owner user in context. Without the -// auth middleware ownerUser(ctx) is empty, so only the tagged-key path creates; -// the user-owned path (which needs an owning API key) is covered by TestAPIv2. -func newKeyTestAPI(t *testing.T) (*Headscale, humatest.TestAPI) { - t.Helper() - - app := createTestApp(t) - - _, api := humatest.New(t, apiv2.Config()) - apiv2.Register(api, apiv2.Backend{State: app.state}) - - return app, api -} - -// createKey POSTs a key and decodes the create response. -func createKey(t *testing.T, api humatest.TestAPI, req apiv2.CreateKeyRequest) apiv2.Key { - t.Helper() - - resp := api.Post("/api/v2/tailnet/-/keys", req) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var key apiv2.Key - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &key)) - - return key -} - -// getKey GETs a key by id and decodes it. -func getKey(t *testing.T, api humatest.TestAPI, id string) apiv2.Key { - t.Helper() - - resp := api.Get("/api/v2/tailnet/-/keys/" + id) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var key apiv2.Key - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &key)) - - return key -} - -// listKeys GETs the key list and returns the enveloped slice. -func listKeys(t *testing.T, api humatest.TestAPI) []apiv2.Key { - t.Helper() - - resp := api.Get("/api/v2/tailnet/-/keys") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var list struct { - Keys []apiv2.Key `json:"keys"` - } - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &list)) - - return list.Keys -} - -func containsKeyID(keys []apiv2.Key, id string) bool { - for _, k := range keys { - if k.ID == id { - return true - } - } - - return false -} - -// srvKey is the server-side ground truth: the stored PreAuthKey, found by its -// stringified id through ListPreAuthKeys (User is preloaded). -func srvKey(t *testing.T, app *Headscale, id string) types.PreAuthKey { - t.Helper() - - want, err := strconv.ParseUint(id, 10, 64) - require.NoError(t, err) - - keys, err := app.state.ListPreAuthKeys() - require.NoError(t, err) - - for _, k := range keys { - if k.ID == want { - return k - } - } - - t.Fatalf("pre-auth key %s not found server-side", id) - - return types.PreAuthKey{} -} - -func keyCount(t *testing.T, app *Headscale) int { - t.Helper() - - keys, err := app.state.ListPreAuthKeys() - require.NoError(t, err) - - return len(keys) -} - -func TestAPIv2Key_Create_Tagged(t *testing.T) { - app, api := newKeyTestAPI(t) - - created := createKey(t, api, apiv2.CreateKeyRequest{ - Description: "dev access", - ExpirySeconds: 86400, - Capabilities: taggedCaps("tag:test"), - }) - - // (a) create response — Tailscale Key shape, exact seconds (int64 wire). - // ID/Key/Created/Expires are server-assigned; assert their presence apart. - want := apiv2.Key{ - KeyType: "auth", - Description: "dev access", - ExpirySeconds: 86400, - Capabilities: *taggedCaps("tag:test"), - Tags: []string{"tag:test"}, - } - if diff := cmp.Diff(want, created, cmpopts.IgnoreFields(apiv2.Key{}, "ID", "Key", "Created", "Expires")); diff != "" { - t.Errorf("created key mismatch (-want +got):\n%s", diff) - } - - assert.NotEmpty(t, created.ID) - assert.NotEmpty(t, created.Key, "secret returned on create") - assert.NotNil(t, created.Expires) - - // (c) server-side — the stored key. - pak := srvKey(t, app, created.ID) - assert.True(t, pak.Reusable) - assert.False(t, pak.Ephemeral) - assert.Equal(t, []string{"tag:test"}, pak.Tags) - assert.Equal(t, "dev access", pak.Description) - assert.Nil(t, pak.User, "tagged key has no owning user") - require.NotNil(t, pak.CreatedAt) - require.NotNil(t, pak.Expiration) - assert.InDelta(t, 86400, pak.Expiration.Sub(*pak.CreatedAt).Seconds(), 2) -} - -func TestAPIv2Key_Create_Permutations(t *testing.T) { - tests := []struct { - name string - req apiv2.CreateKeyRequest - wantReusable bool - wantEphemeral bool - wantDesc string - wantSeconds float64 - }{ - { - name: "single-use", - req: apiv2.CreateKeyRequest{Capabilities: &apiv2.KeyCapabilities{ - Devices: apiv2.KeyDeviceCapabilities{Create: apiv2.KeyDeviceCreateCapabilities{Tags: []string{"tag:test"}}}, - }}, - wantSeconds: 7776000, - }, - { - name: "reusable", - req: apiv2.CreateKeyRequest{Capabilities: taggedCaps("tag:test")}, - wantReusable: true, - wantSeconds: 7776000, - }, - { - name: "ephemeral", - req: apiv2.CreateKeyRequest{Capabilities: &apiv2.KeyCapabilities{ - Devices: apiv2.KeyDeviceCapabilities{Create: apiv2.KeyDeviceCreateCapabilities{ - Ephemeral: true, - Tags: []string{"tag:test"}, - }}, - }}, - wantEphemeral: true, - wantSeconds: 7776000, - }, - { - name: "with description", - req: apiv2.CreateKeyRequest{Description: "ci", Capabilities: taggedCaps("tag:test")}, - wantReusable: true, - wantDesc: "ci", - wantSeconds: 7776000, - }, - { - name: "explicit expiry", - req: apiv2.CreateKeyRequest{ExpirySeconds: 3600, Capabilities: taggedCaps("tag:test")}, - wantReusable: true, - wantSeconds: 3600, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - app, api := newKeyTestAPI(t) - - created := createKey(t, api, tt.req) - assert.Equal(t, tt.wantReusable, created.Capabilities.Devices.Create.Reusable) - assert.Equal(t, tt.wantEphemeral, created.Capabilities.Devices.Create.Ephemeral) - assert.Equal(t, tt.wantDesc, created.Description) - assert.InDelta(t, tt.wantSeconds, float64(created.ExpirySeconds), 2) - - pak := srvKey(t, app, created.ID) - assert.Equal(t, tt.wantReusable, pak.Reusable) - assert.Equal(t, tt.wantEphemeral, pak.Ephemeral) - assert.Equal(t, tt.wantDesc, pak.Description) - require.NotNil(t, pak.CreatedAt) - require.NotNil(t, pak.Expiration) - assert.InDelta(t, tt.wantSeconds, pak.Expiration.Sub(*pak.CreatedAt).Seconds(), 2) - }) - } -} - -func TestAPIv2Key_Get(t *testing.T) { - _, api := newKeyTestAPI(t) - - created := createKey(t, api, apiv2.CreateKeyRequest{ - Description: "dev access", - ExpirySeconds: 86400, - Capabilities: taggedCaps("tag:test"), - }) - - got := getKey(t, api, created.ID) - // Get omits the secret (empty Key) and is stable across the round-trip. - want := apiv2.Key{ - ID: created.ID, - KeyType: "auth", - Description: "dev access", - ExpirySeconds: 86400, - Capabilities: *taggedCaps("tag:test"), - Tags: []string{"tag:test"}, - } - if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(apiv2.Key{}, "Created", "Expires")); diff != "" { - t.Errorf("got key mismatch (-want +got):\n%s", diff) - } - - // Unknown id and bad tailnet both 404 with the Tailscale error body. - assert.Equal(t, http.StatusNotFound, api.Get("/api/v2/tailnet/-/keys/999999").Code) - bad := api.Get("/api/v2/tailnet/example.com/keys/" + created.ID) - assert.Equal(t, http.StatusNotFound, bad.Code) - assert.Contains(t, bad.Body.String(), `"message"`) -} - -func TestAPIv2Key_List(t *testing.T) { - app, api := newKeyTestAPI(t) - - k1 := createKey(t, api, apiv2.CreateKeyRequest{Capabilities: taggedCaps("tag:test")}) - k2 := createKey(t, api, apiv2.CreateKeyRequest{Capabilities: taggedCaps("tag:test")}) - - keys := listKeys(t, api) - assert.True(t, containsKeyID(keys, k1.ID)) - assert.True(t, containsKeyID(keys, k2.ID)) - - // Server-side has both too. - assert.GreaterOrEqual(t, keyCount(t, app), 2) - - bad := api.Get("/api/v2/tailnet/example.com/keys") - assert.Equal(t, http.StatusNotFound, bad.Code) - assert.Contains(t, bad.Body.String(), `"message"`) -} - -func TestAPIv2Key_Delete(t *testing.T) { - app, api := newKeyTestAPI(t) - - created := createKey(t, api, apiv2.CreateKeyRequest{Capabilities: taggedCaps("tag:test")}) - - require.Equal(t, http.StatusOK, api.Delete("/api/v2/tailnet/-/keys/"+created.ID).Code) - - // DELETE soft-revokes (Tailscale-faithful): the key stays retrievable with - // invalid set and a revoked timestamp, and is still listed. - got := getKey(t, api, created.ID) - assert.True(t, got.Invalid, "revoked key reports invalid") - assert.NotNil(t, got.Revoked, "revoked timestamp is populated") - assert.True(t, containsKeyID(listKeys(t, api), created.ID), "revoked key still listed") - - // Server-side: row kept, revoked stamped. - pak := srvKey(t, app, created.ID) - require.NotNil(t, pak.Revoked) - require.Error(t, pak.Validate(), "revoked key is invalid") - - // Delete again -> 404 (already revoked). - assert.Equal(t, http.StatusNotFound, api.Delete("/api/v2/tailnet/-/keys/"+created.ID).Code) - - // The collector hard-deletes keys revoked before the cutoff. - reaped, err := app.state.DestroyRevokedPreAuthKeysBefore(time.Now().Add(time.Minute)) - require.NoError(t, err) - assert.GreaterOrEqual(t, reaped, 1) - assert.False(t, containsKeyID(listKeys(t, api), created.ID), "collector removed the revoked key") - assert.Equal(t, http.StatusNotFound, api.Get("/api/v2/tailnet/-/keys/"+created.ID).Code) -} - -func TestAPIv2Key_Create_NoTags_NoOwner_400(t *testing.T) { - app, api := newKeyTestAPI(t) - - before := keyCount(t, app) - - resp := api.Post("/api/v2/tailnet/-/keys", apiv2.CreateKeyRequest{Capabilities: &apiv2.KeyCapabilities{}}) - assert.Equal(t, http.StatusBadRequest, resp.Code) - assert.Contains(t, resp.Body.String(), `"message"`) - - // No orphan key was created. - assert.Equal(t, before, keyCount(t, app)) -} diff --git a/hscontrol/apiv2_oauth_matrix_test.go b/hscontrol/apiv2_oauth_matrix_test.go deleted file mode 100644 index 3e71d730f..000000000 --- a/hscontrol/apiv2_oauth_matrix_test.go +++ /dev/null @@ -1,280 +0,0 @@ -package hscontrol - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "testing" - "time" - - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/scope" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// apiReq issues an arbitrary-method request with an optional bearer token and JSON -// body, returning the status and raw body. It owns the response body. -func apiReq(t *testing.T, method, target, bearer string, body any) (int, []byte) { - t.Helper() - - var r io.Reader - - if body != nil { - b, err := json.Marshal(body) - require.NoError(t, err) - - r = bytes.NewReader(b) - } - - req, err := http.NewRequestWithContext(t.Context(), method, target, r) - require.NoError(t, err) - - if body != nil { - req.Header.Set("Content-Type", "application/json") - } - - if bearer != "" { - req.Header.Set("Authorization", "Bearer "+bearer) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - data, _ := io.ReadAll(resp.Body) - - return resp.StatusCode, data -} - -// scopeDenied reports whether a response is a scope-enforcement denial. The scope -// gate runs in the middleware before the handler, so a denial is exactly a 403 -// carrying this message; any other status (200, or a 400/404 from the handler on -// minimal input) means the request passed the scope gate. -func scopeDenied(status int, body []byte) bool { - return status == http.StatusForbidden && - strings.Contains(string(body), "missing the required scope") -} - -// mintScopedToken creates an OAuth client holding exactly scope s (tagged tag:ci so -// tag-requiring scopes are valid) and returns an access token for it. -func mintScopedToken(t *testing.T, baseURL, admin string, s scope.Scope) string { - t.Helper() - - _, secret := createClient(t, baseURL, admin, []string{string(s)}, []string{"tag:ci"}) - - return tokenFor(t, baseURL, secret) -} - -// createAdminAuthKey creates a tagged auth key with the admin key and returns its id. -func createAdminAuthKey(t *testing.T, baseURL, admin string) string { - t.Helper() - - status, body := apiReq(t, http.MethodPost, baseURL+"/api/v2/tailnet/-/keys", admin, apiv2.CreateKeyRequest{ - Capabilities: &apiv2.KeyCapabilities{Devices: apiv2.KeyDeviceCapabilities{ - Create: apiv2.KeyDeviceCreateCapabilities{Reusable: true, Tags: []string{"tag:ci"}}, - }}, - }) - require.Equalf(t, http.StatusOK, status, "create admin auth key: %s", body) - - var key apiv2.Key - require.NoError(t, json.Unmarshal(body, &key)) - - return key.ID -} - -type matrixOp struct { - name string - method string - path string - need scope.Scope - body any - // multiplexed marks the keyType-multiplexed keys ops. They self-enforce in - // the handler and, to avoid an existence oracle, deny an unauthorized token - // with a uniform not-found rather than the middleware's scope-403. So denial - // is "resource not returned" (status != 200), not a specific 403. - multiplexed bool -} - -// TestAPIv2OAuthMatrix_Enforcement is the exhaustive operation×scope cross-product: -// for every scope-gated operation and every scope in the vocabulary, a token -// holding exactly that scope is allowed iff scope.Grants permits it (P3) and denied -// otherwise (P2). -func TestAPIv2OAuthMatrix_Enforcement(t *testing.T) { - app, baseURL, admin := newOAuthTestServer(t) - - // Stable ids for the keyType-multiplexed get-by-id operations. - clientID, _ := createClient(t, baseURL, admin, []string{"oauth_keys:read"}, nil) - authKeyID := createAdminAuthKey(t, baseURL, admin) - _ = app - - ops := []matrixOp{ - {"getDevice", http.MethodGet, "/api/v2/device/1", scope.DevicesCoreRead, nil, false}, - {"listDevices", http.MethodGet, "/api/v2/tailnet/-/devices", scope.DevicesCoreRead, nil, false}, - {"deleteDevice", http.MethodDelete, "/api/v2/device/1", scope.DevicesCore, nil, false}, - {"authorizeDevice", http.MethodPost, "/api/v2/device/1/authorized", scope.DevicesCore, map[string]any{}, false}, - {"setDeviceName", http.MethodPost, "/api/v2/device/1/name", scope.DevicesCore, map[string]any{}, false}, - {"setDeviceTags", http.MethodPost, "/api/v2/device/1/tags", scope.DevicesCore, map[string]any{}, false}, - {"setDeviceKey", http.MethodPost, "/api/v2/device/1/key", scope.DevicesCore, map[string]any{}, false}, - {"setDeviceRoutes", http.MethodPost, "/api/v2/device/1/routes", scope.DevicesRoutes, map[string]any{}, false}, - {"getDeviceRoutes", http.MethodGet, "/api/v2/device/1/routes", scope.DevicesRoutesRead, nil, false}, - {"getACL", http.MethodGet, "/api/v2/tailnet/-/acl", scope.PolicyFileRead, nil, false}, - {"setACL", http.MethodPost, "/api/v2/tailnet/-/acl", scope.PolicyFile, map[string]any{}, false}, - {"getSettings", http.MethodGet, "/api/v2/tailnet/-/settings", scope.FeatureSettingsRead, nil, false}, - {"updateSettings", http.MethodPatch, "/api/v2/tailnet/-/settings", scope.FeatureSettings, map[string]any{}, false}, - {"getKeyClient", http.MethodGet, "/api/v2/tailnet/-/keys/" + clientID, scope.OAuthKeysRead, nil, true}, - {"getKeyAuth", http.MethodGet, "/api/v2/tailnet/-/keys/" + authKeyID, scope.AuthKeysRead, nil, true}, - } - - // One token per scope, reused across every operation. - tokens := make(map[scope.Scope]string, len(scope.Known())) - for _, s := range scope.Known() { - tokens[s] = mintScopedToken(t, baseURL, admin, s) - } - - for _, op := range ops { - for _, s := range scope.Known() { - status, body := apiReq(t, op.method, baseURL+op.path, tokens[s], op.body) - - wantDenied := !scope.Grants([]scope.Scope{s}, op.need) - - // A multiplexed keys op denies via uniform not-found (no existence - // oracle), so denial is "resource not returned"; every other op - // denies with the middleware's scope-403. - denied := scopeDenied(status, body) - if op.multiplexed { - denied = status != http.StatusOK - } - - assert.Equalf(t, wantDenied, denied, - "op %s with scope %q (needs %q): status=%d body=%s", - op.name, s, op.need, status, body) - } - } -} - -// TestAPIv2OAuthMatrix_ScopeNarrowing proves P1 for token minting: a client may -// only mint a token for scopes within its own grant. For every held scope X and -// requested scope Y, the mint succeeds iff scope.Grants([X], Y). -func TestAPIv2OAuthMatrix_ScopeNarrowing(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - for _, held := range scope.Known() { - _, secret := createClient(t, baseURL, admin, []string{string(held)}, []string{"tag:ci"}) - - for _, want := range scope.Known() { - status, m := mintToken(t, baseURL, "", secret, string(want), false) - - wantOK := scope.Grants([]scope.Scope{held}, want) - if wantOK { - assert.Equalf(t, http.StatusOK, status, "held %q narrow to %q: %v", held, want, m) - } else { - assert.Equalf(t, http.StatusBadRequest, status, - "held %q must not mint %q: %v", held, want, m) - assert.Equal(t, "invalid_scope", m["error"]) - } - } - } -} - -// TestAPIv2OAuthMatrix_TagNarrowing proves P1 for tags at mint time: a client may -// only mint a token for tags within its grant (closing the /oauth/token tags-param -// path). A client with the "all" scope may request any tag. -func TestAPIv2OAuthMatrix_TagNarrowing(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - _, secret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:a", "tag:b"}) - - // In-grant tags mint; an out-of-grant tag is rejected. - status, m := mintToken(t, baseURL, "", secret, "", false) - require.Equalf(t, http.StatusOK, status, "%v", m) - - for _, tag := range []string{"tag:a", "tag:b"} { - st, mm := mintTokenWithTags(t, baseURL, secret, tag) - assert.Equalf(t, http.StatusOK, st, "in-grant tag %q: %v", tag, mm) - } - - st, mm := mintTokenWithTags(t, baseURL, secret, "tag:c") - assert.Equal(t, http.StatusBadRequest, st, mm) - assert.Equal(t, "invalid_target", mm["error"]) - - // An all-scope client may request any tag. - _, allSecret := createClient(t, baseURL, admin, []string{"all"}, []string{"tag:a"}) - stAll, mAll := mintTokenWithTags(t, baseURL, allSecret, "tag:anything") - assert.Equalf(t, http.StatusOK, stAll, "all-scope client may assign any tag: %v", mAll) -} - -// TestAPIv2OAuthMatrix_Lifecycle proves expired and revoked credentials are denied -// at the HTTP layer, and that an admin API key bypasses scope checks. -func TestAPIv2OAuthMatrix_Lifecycle(t *testing.T) { - app, baseURL, admin := newOAuthTestServer(t) - - devicesPath := baseURL + "/api/v2/tailnet/-/devices" - - // Expired token → 401. - _, client, err := app.state.CreateOAuthClient([]string{"devices:core:read"}, []string{"tag:ci"}, "expired", nil) - require.NoError(t, err) - - past := time.Now().Add(-time.Hour) - expiredTok, _, err := app.state.MintAccessToken(client.ClientID, []string{"devices:core:read"}, nil, &past) - require.NoError(t, err) - - status, _ := apiReq(t, http.MethodGet, devicesPath, expiredTok, nil) - assert.Equal(t, http.StatusUnauthorized, status, "expired token must be rejected") - - // Revoked client → its token is denied. - _, revClient, err := app.state.CreateOAuthClient([]string{"devices:core:read"}, []string{"tag:ci"}, "revoked", nil) - require.NoError(t, err) - - future := time.Now().Add(time.Hour) - revTok, _, err := app.state.MintAccessToken(revClient.ClientID, []string{"devices:core:read"}, nil, &future) - require.NoError(t, err) - - // Works before revocation. - okStatus, okBody := apiReq(t, http.MethodGet, devicesPath, revTok, nil) - assert.Falsef(t, scopeDenied(okStatus, okBody), "token should pass the scope gate before revoke: %d", okStatus) - - require.NoError(t, app.state.RevokeOAuthClient(revClient.ClientID)) - - revStatus, _ := apiReq(t, http.MethodGet, devicesPath, revTok, nil) - assert.Equal(t, http.StatusUnauthorized, revStatus, "token of a revoked client must be rejected") - - // Admin API key bypasses scope checks: it reaches a devices op that a minimal - // (feature_settings:read) token cannot. - minimalTok := mintScopedToken(t, baseURL, admin, scope.FeatureSettingsRead) - minStatus, minBody := apiReq(t, http.MethodGet, devicesPath, minimalTok, nil) - assert.True(t, scopeDenied(minStatus, minBody), "minimal token must be scope-denied on devices") - - adminStatus, adminBody := apiReq(t, http.MethodGet, devicesPath, admin, nil) - assert.Falsef(t, scopeDenied(adminStatus, adminBody), - "admin key is all-access and must not be scope-denied: %d %s", adminStatus, adminBody) -} - -// mintTokenWithTags mints with a tags narrowing parameter. -func mintTokenWithTags(t *testing.T, baseURL, secret, tags string) (int, map[string]any) { - t.Helper() - - form := fmt.Sprintf("grant_type=client_credentials&client_secret=%s&tags=%s", secret, tags) - - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, - baseURL+"/api/v2/oauth/token", strings.NewReader(form)) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - data, _ := io.ReadAll(resp.Body) - - var m map[string]any - - _ = json.Unmarshal(data, &m) - - return resp.StatusCode, m -} diff --git a/hscontrol/apiv2_oauth_test.go b/hscontrol/apiv2_oauth_test.go deleted file mode 100644 index c3f61043c..000000000 --- a/hscontrol/apiv2_oauth_test.go +++ /dev/null @@ -1,511 +0,0 @@ -package hscontrol - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// newOAuthTestServer builds the full v2 API (the auth+scope middleware and the -// OAuth token endpoint, which the humatest harness in apiv2_keys_test.go does -// not mount) over a real httptest server, returning the app, its base URL, and -// an all-access admin API key. -func newOAuthTestServer(t *testing.T) (*Headscale, string, string) { - t.Helper() - - app := createTestApp(t) - - // Tag creation now requires the tag to exist in policy (matching - // SetNodeTags), so define the tags these tests assign. Tests needing - // specific tag ownership (e.g. delegation) override this policy. - const policy = `{"tagOwners":{"tag:a":[],"tag:b":[],"tag:c":[],"tag:ci":[],"tag:k8s":[],"tag:k8s-operator":[],"tag:other":[],"tag:anything":[]},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - - _, err := app.state.SetPolicy([]byte(policy)) - require.NoError(t, err) - _, err = app.state.SetPolicyInDB(policy) - require.NoError(t, err) - _, err = app.state.ReloadPolicy() - require.NoError(t, err) - - mux, _ := apiv2.Handler(apiv2.Backend{State: app.state, Change: app.Change, Cfg: app.cfg}) - srv := httptest.NewServer(mux) - t.Cleanup(srv.Close) - - adminKey, _, err := app.state.CreateAPIKey(nil) - require.NoError(t, err) - - return app, srv.URL, adminKey -} - -// apiPost POSTs a JSON body with an optional bearer token, returning the status -// and raw body. It owns the response body so callers never leak it. -func apiPost(t *testing.T, target, bearer string, body any) (int, []byte) { - t.Helper() - - b, err := json.Marshal(body) - require.NoError(t, err) - - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, target, bytes.NewReader(b)) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/json") - - if bearer != "" { - req.Header.Set("Authorization", "Bearer "+bearer) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - data, _ := io.ReadAll(resp.Body) - - return resp.StatusCode, data -} - -// apiGet GETs a target with an optional bearer token, returning the status. It -// drains and closes the response body so callers never leak it. -func apiGet(t *testing.T, target, bearer string) int { - t.Helper() - - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, target, nil) - require.NoError(t, err) - - if bearer != "" { - req.Header.Set("Authorization", "Bearer "+bearer) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - _, _ = io.Copy(io.Discard, resp.Body) - - return resp.StatusCode -} - -// createClient creates an OAuth client through the v2 keys API as the bearer -// credential, returning the client id and the once-shown secret. -func createClient(t *testing.T, baseURL, bearer string, scopes, tags []string) (string, string) { - t.Helper() - - status, body := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", bearer, apiv2.CreateKeyRequest{ - KeyType: "client", - Scopes: scopes, - Tags: tags, - }) - require.Equalf(t, http.StatusOK, status, "create client: %s", body) - - var key apiv2.Key - require.NoError(t, json.Unmarshal(body, &key)) - assert.Equal(t, "client", key.KeyType) - require.NotEmpty(t, key.ID, "client id returned") - require.NotEmpty(t, key.Key, "secret returned once on create") - - return key.ID, key.Key -} - -// mintToken runs the client-credentials grant. scope is an optional space-delimited -// narrowing of the client's scopes. credsInBasic sends the secret via HTTP Basic -// instead of the body. Returns the status and decoded JSON body. -func mintToken(t *testing.T, baseURL, clientID, secret, scope string, credsInBasic bool) (int, map[string]any) { - t.Helper() - - form := url.Values{"grant_type": {"client_credentials"}} - if scope != "" { - form.Set("scope", scope) - } - - if !credsInBasic { - form.Set("client_secret", secret) - } - - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, - baseURL+"/api/v2/oauth/token", strings.NewReader(form.Encode())) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - if credsInBasic { - req.SetBasicAuth(clientID, secret) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - data, _ := io.ReadAll(resp.Body) - - var m map[string]any - - _ = json.Unmarshal(data, &m) - - return resp.StatusCode, m -} - -// tokenFor mints and returns a bearer access token for the client. -func tokenFor(t *testing.T, baseURL, secret string) string { - t.Helper() - - status, m := mintToken(t, baseURL, "", secret, "", false) - require.Equalf(t, http.StatusOK, status, "mint token: %v", m) - - tok, _ := m["access_token"].(string) - require.NotEmpty(t, tok) - - return tok -} - -// createTaggedKey attempts to create a tagged auth key and returns the HTTP -// status, so allow/deny is asserted by the caller. -func createTaggedKey(t *testing.T, baseURL, bearer string, tags []string) int { - t.Helper() - - status, _ := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", bearer, apiv2.CreateKeyRequest{ - Capabilities: &apiv2.KeyCapabilities{Devices: apiv2.KeyDeviceCapabilities{ - Create: apiv2.KeyDeviceCreateCapabilities{Reusable: true, Tags: tags}, - }}, - }) - - return status -} - -func TestAPIv2OAuth_TokenEndpoint(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - clientID, secret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:ci"}) - - // The secret embeds the client id (Tailscale's derive-from-secret trick). - assert.Contains(t, secret, clientID) - - // Mint via the request body. - status, m := mintToken(t, baseURL, clientID, secret, "", false) - require.Equalf(t, http.StatusOK, status, "%v", m) - assert.Equal(t, "Bearer", m["token_type"]) - assert.InDelta(t, 3600, m["expires_in"], 0, "fixed 1h, in seconds") - tok, _ := m["access_token"].(string) - assert.Contains(t, tok, "hskey-oauthtok-", "distinct prefix from admin keys") - - // Mint via HTTP Basic (x/oauth2 auto-detect probes Basic first). - statusBasic, mBasic := mintToken(t, baseURL, clientID, secret, "", true) - require.Equalf(t, http.StatusOK, statusBasic, "%v", mBasic) - assert.NotEmpty(t, mBasic["access_token"]) - - // Bad credentials → RFC 6749 invalid_client. - statusBad, mBad := mintToken(t, baseURL, clientID, "hskey-client-deadbeefdead-"+stringOf("0", 64), "", false) - assert.Equal(t, http.StatusUnauthorized, statusBad) - assert.Equal(t, "invalid_client", mBad["error"]) - - // The token response carries a bearer credential and must not be cached - // (RFC 6749 §5.1). mintToken consumes the body, so probe the header raw. - form := url.Values{"grant_type": {"client_credentials"}, "client_secret": {secret}} - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, - baseURL+"/api/v2/oauth/token", strings.NewReader(form.Encode())) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - resp.Body.Close() - assert.Equal(t, "no-store", resp.Header.Get("Cache-Control")) -} - -func TestAPIv2OAuth_ScopeEnforcement(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - // A write-scoped token may create an auth key within its tag grant... - _, writeSecret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:ci"}) - writeTok := tokenFor(t, baseURL, writeSecret) - assert.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, writeTok, []string{"tag:ci"}), - "write scope + in-grant tag allowed") - - // ...but not with a tag outside its grant. - assert.Equal(t, http.StatusForbidden, createTaggedKey(t, baseURL, writeTok, []string{"tag:other"}), - "tag outside grant denied") - - // A read-only token is denied the write. - _, readSecret := createClient(t, baseURL, admin, []string{"auth_keys:read"}, []string{"tag:ci"}) - readTok := tokenFor(t, baseURL, readSecret) - assert.Equal(t, http.StatusForbidden, createTaggedKey(t, baseURL, readTok, []string{"tag:ci"}), - "read scope denied write") - - // The wrong write scope (devices:core) cannot create auth keys. - _, devSecret := createClient(t, baseURL, admin, []string{"devices:core"}, []string{"tag:ci"}) - devTok := tokenFor(t, baseURL, devSecret) - assert.Equal(t, http.StatusForbidden, createTaggedKey(t, baseURL, devTok, []string{"tag:ci"}), - "wrong scope denied") - - // The "all" super-scope grants auth_keys. - _, allSecret := createClient(t, baseURL, admin, []string{"all"}, []string{"tag:ci"}) - allTok := tokenFor(t, baseURL, allSecret) - assert.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, allTok, []string{"tag:ci"}), - "all grants auth_keys") - - // A token minted from the all-powerful client but narrowed to read-only is - // itself denied writes: narrowing is enforced on the minted token. - statusNarrow, mNarrow := mintToken(t, baseURL, "", allSecret, "auth_keys:read", false) - require.Equalf(t, http.StatusOK, statusNarrow, "narrow mint: %v", mNarrow) - narrowed, _ := mNarrow["access_token"].(string) - assert.Equal(t, http.StatusForbidden, createTaggedKey(t, baseURL, narrowed, []string{"tag:ci"}), - "token narrowed to :read denied write") -} - -func TestAPIv2OAuth_ClientManagementScopes(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - // An oauth_keys token may create a client within its own grant... - _, okSecret := createClient(t, baseURL, admin, []string{"oauth_keys"}, []string{"tag:ci"}) - okTok := tokenFor(t, baseURL, okSecret) - status, body := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", okTok, apiv2.CreateKeyRequest{ - KeyType: "client", Scopes: []string{"oauth_keys:read"}, - }) - assert.Equalf(t, http.StatusOK, status, "oauth_keys creates an in-grant client: %s", body) - - // ...but may NOT escalate by granting the new client a scope the token lacks. - statusEsc, _ := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", okTok, apiv2.CreateKeyRequest{ - KeyType: "client", Scopes: []string{"auth_keys"}, Tags: []string{"tag:ci"}, - }) - assert.Equal(t, http.StatusForbidden, statusEsc, "oauth_keys token cannot mint a broader client") - - // A token without oauth_keys cannot manage clients at all. - _, akSecret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:ci"}) - akTok := tokenFor(t, baseURL, akSecret) - statusDeny, _ := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", akTok, apiv2.CreateKeyRequest{ - KeyType: "client", Scopes: []string{"auth_keys"}, Tags: []string{"tag:ci"}, - }) - assert.Equal(t, http.StatusForbidden, statusDeny, "auth_keys (no oauth_keys) cannot create a client") -} - -func TestAPIv2OAuth_TagOwnedBy(t *testing.T) { - app, baseURL, admin := newOAuthTestServer(t) - - // tag:k8s is owned by tag:k8s-operator: the operator's tag delegation. - // tag:other exists but is owned by no one, so it tests grant denial (403) - // rather than tag-not-in-policy (400). - const policy = `{"tagOwners":{"tag:k8s-operator":[],"tag:k8s":["tag:k8s-operator"],"tag:other":[]},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - - _, err := app.state.SetPolicy([]byte(policy)) - require.NoError(t, err) - _, err = app.state.SetPolicyInDB(policy) - require.NoError(t, err) - _, err = app.state.ReloadPolicy() - require.NoError(t, err) - - _, secret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:k8s-operator"}) - tok := tokenFor(t, baseURL, secret) - - // Exact-match tag: allowed. - assert.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, tok, []string{"tag:k8s-operator"}), - "a token may use its own tag") - - // Owned-by tag: allowed (tag:k8s is owned by tag:k8s-operator). - assert.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, tok, []string{"tag:k8s"}), - "a token may use a tag owned by its tag") - - // Unrelated tag: denied. - assert.Equal(t, http.StatusForbidden, createTaggedKey(t, baseURL, tok, []string{"tag:other"}), - "a token may not use an unowned tag") -} - -// TestAPIv2OAuth_NoClientExistenceOracle asserts a token that cannot read OAuth -// clients gets the same response for a real client id as for a missing key, so -// it cannot enumerate which ids are OAuth clients. -func TestAPIv2OAuth_NoClientExistenceOracle(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - clientID, _ := createClient(t, baseURL, admin, []string{"oauth_keys"}, []string{"tag:ci"}) - - // A token holding only auth_keys:read (no oauth_keys:read). - _, akSecret := createClient(t, baseURL, admin, []string{"auth_keys:read"}, []string{"tag:ci"}) - akTok := tokenFor(t, baseURL, akSecret) - - statusReal := apiGet(t, baseURL+"/api/v2/tailnet/-/keys/"+clientID, akTok) - statusMissing := apiGet(t, baseURL+"/api/v2/tailnet/-/keys/deadbeefdead", akTok) - - assert.Equal(t, statusMissing, statusReal, - "a token without oauth_keys:read must not distinguish a real client id from a missing key") - assert.NotEqual(t, http.StatusOK, statusReal, "the client must not be readable") - - // A token that does hold oauth_keys:read can read the client. - _, okSecret := createClient(t, baseURL, admin, []string{"oauth_keys:read"}, []string{"tag:ci"}) - okTok := tokenFor(t, baseURL, okSecret) - - statusOK := apiGet(t, baseURL+"/api/v2/tailnet/-/keys/"+clientID, okTok) - assert.Equal(t, http.StatusOK, statusOK, "oauth_keys:read may read the client") -} - -// TestAPIv2OAuth_SetDeviceTagsGrant asserts a devices:core token may only set -// tags within its grant on a device. Without the grant check, the scope alone -// would let a token stamp any existing policy tag (e.g. tag:other) onto any node. -func TestAPIv2OAuth_SetDeviceTagsGrant(t *testing.T) { - app, baseURL, admin := newOAuthTestServer(t) - - // A registered, user-owned node to retag. - user := app.state.CreateUserForTest("dut") - node := app.state.CreateRegisteredNodeForTest(user, "dut") - node.User = user - view := app.state.PutNodeInStoreForTest(*node) - devURL := baseURL + "/api/v2/device/" + view.StringID() + "/tags" - - // A devices:core token granted only tag:ci. - _, secret := createClient(t, baseURL, admin, []string{"devices:core"}, []string{"tag:ci"}) - tok := tokenFor(t, baseURL, secret) - - // In-grant tag is allowed. - st, body := apiPost(t, devURL, tok, map[string]any{"tags": []string{"tag:ci"}}) - assert.Equalf(t, http.StatusOK, st, "in-grant tag: %s", body) - - // An existing policy tag outside the token's grant is denied, not silently set. - st, _ = apiPost(t, devURL, tok, map[string]any{"tags": []string{"tag:other"}}) - assert.Equal(t, http.StatusForbidden, st, "out-of-grant tag must be denied") - - // An admin API key is unrestricted. - st, body = apiPost(t, devURL, admin, map[string]any{"tags": []string{"tag:other"}}) - assert.Equalf(t, http.StatusOK, st, "admin may set any policy tag: %s", body) -} - -// TestAPIv2OAuth_UndefinedTagRejected asserts an OAuth token cannot create a -// client or auth key carrying a tag absent from policy (matching SetNodeTags), -// while an admin key retains the historical syntax-only validation. -func TestAPIv2OAuth_UndefinedTagRejected(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - - // A token that may create clients and auth keys, holding tag:ci (in policy). - _, secret := createClient(t, baseURL, admin, []string{"oauth_keys", "auth_keys"}, []string{"tag:ci"}) - tok := tokenFor(t, baseURL, secret) - - // A token-created auth key with a tag not in policy is rejected. - assert.Equal(t, http.StatusBadRequest, createTaggedKey(t, baseURL, tok, []string{"tag:undefined"}), - "token may not create an auth key with an undefined tag") - - // A token-created client with a tag not in policy is rejected. - st, _ := apiPost(t, baseURL+"/api/v2/tailnet/-/keys", tok, apiv2.CreateKeyRequest{ - KeyType: "client", Scopes: []string{"auth_keys"}, Tags: []string{"tag:undefined"}, - }) - assert.Equal(t, http.StatusBadRequest, st, "token may not create a client with an undefined tag") - - // An admin key keeps the historical behaviour: a syntactically valid but - // undefined tag is accepted (consistent with the v1/CLI pre-auth-key path). - assert.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, admin, []string{"tag:adminhistorical"}), - "admin retains syntax-only tag validation") -} - -// TestAPIv2OAuth_DenialBranches covers the token-endpoint and bearer-dispatch -// failure paths: each must fail closed with the right status and no session. -func TestAPIv2OAuth_DenialBranches(t *testing.T) { - _, baseURL, _ := newOAuthTestServer(t) - tokenURL := baseURL + "/api/v2/oauth/token" - keysURL := baseURL + "/api/v2/tailnet/-/keys" - - post := func(form string) (int, map[string]any) { - req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, tokenURL, strings.NewReader(form)) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - data, _ := io.ReadAll(resp.Body) - - var m map[string]any - - _ = json.Unmarshal(data, &m) - - return resp.StatusCode, m - } - - // No credentials → invalid_client. - st, m := post("grant_type=client_credentials") - assert.Equal(t, http.StatusUnauthorized, st) - assert.Equal(t, "invalid_client", m["error"]) - - // Wrong grant_type → unsupported_grant_type. - st, m = post("grant_type=authorization_code&client_secret=x") - assert.Equal(t, http.StatusBadRequest, st) - assert.Equal(t, "unsupported_grant_type", m["error"]) - - // Unknown secret → invalid_client. - st, m = post("grant_type=client_credentials&client_secret=not-a-real-secret") - assert.Equal(t, http.StatusUnauthorized, st) - assert.Equal(t, "invalid_client", m["error"]) - - // Bearer dispatch: every malformed/unknown bearer is unauthorized. - for _, bearer := range []string{ - "hskey-oauthtok-deadbeefdead-" + stringOf("0", 64), // well-formed prefix, unknown token - "hskey-oauthtok-garbage", // malformed OAuth token - "hskey-client-deadbeefdead-" + stringOf("0", 64), // client secret presented as API bearer - "totally-bogus", - } { - assert.Equalf(t, http.StatusUnauthorized, apiGet(t, keysURL, bearer), - "bearer %q must be unauthorized", bearer) - } -} - -// TestAPIv2OAuth_KeysMultiplexIsolation asserts the multiplexed keys endpoint -// keeps the two kinds isolated by scope: a token cannot list or delete a kind it -// lacks the scope for, and an admin key sees both. -func TestAPIv2OAuth_KeysMultiplexIsolation(t *testing.T) { - _, baseURL, admin := newOAuthTestServer(t) - keysURL := baseURL + "/api/v2/tailnet/-/keys" - - clientID, _ := createClient(t, baseURL, admin, []string{"oauth_keys"}, []string{"tag:ci"}) - require.Equal(t, http.StatusOK, createTaggedKey(t, baseURL, admin, []string{"tag:ci"})) - - listKinds := func(bearer string) map[string]int { - st, body := apiReq(t, http.MethodGet, keysURL, bearer, nil) - require.Equalf(t, http.StatusOK, st, "%s", body) - - var out struct { - Keys []apiv2.Key `json:"keys"` - } - - require.NoError(t, json.Unmarshal(body, &out)) - - kinds := map[string]int{} - for _, k := range out.Keys { - kinds[k.KeyType]++ - } - - return kinds - } - - _, akReadSecret := createClient(t, baseURL, admin, []string{"auth_keys:read"}, []string{"tag:ci"}) - akKinds := listKinds(tokenFor(t, baseURL, akReadSecret)) - assert.Zero(t, akKinds["client"], "auth_keys:read must not see OAuth clients") - assert.Positive(t, akKinds["auth"], "auth_keys:read sees auth keys") - - _, okReadSecret := createClient(t, baseURL, admin, []string{"oauth_keys:read"}, []string{"tag:ci"}) - okKinds := listKinds(tokenFor(t, baseURL, okReadSecret)) - assert.Zero(t, okKinds["auth"], "oauth_keys:read must not see auth keys") - assert.Positive(t, okKinds["client"], "oauth_keys:read sees OAuth clients") - - adminKinds := listKinds(admin) - assert.Positive(t, adminKinds["auth"]) - assert.Positive(t, adminKinds["client"]) - - // An auth_keys (write) token cannot delete an OAuth client; it survives. - _, akWriteSecret := createClient(t, baseURL, admin, []string{"auth_keys"}, []string{"tag:ci"}) - stDel, _ := apiReq(t, http.MethodDelete, keysURL+"/"+clientID, tokenFor(t, baseURL, akWriteSecret), nil) - assert.NotEqual(t, http.StatusOK, stDel, "auth_keys token must not delete an OAuth client") - assert.Positive(t, listKinds(admin)["client"], "client survives the denied delete") - - // An oauth_keys (write) token deletes the client. - _, okWriteSecret := createClient(t, baseURL, admin, []string{"oauth_keys"}, []string{"tag:ci"}) - stDel2, body2 := apiReq(t, http.MethodDelete, keysURL+"/"+clientID, tokenFor(t, baseURL, okWriteSecret), nil) - assert.Equalf(t, http.StatusOK, stDel2, "oauth_keys deletes the client: %s", body2) -} - -func stringOf(s string, n int) string { - return strings.Repeat(s, n) -} diff --git a/hscontrol/apiv2_settings_test.go b/hscontrol/apiv2_settings_test.go deleted file mode 100644 index 62b88bd75..000000000 --- a/hscontrol/apiv2_settings_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/danielgtaylor/huma/v2/humatest" - "github.com/google/go-cmp/cmp" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// settingsAPIWithConfig registers the v2 API over a Backend carrying cfg and a -// zero State — the settings GET reads only b.Cfg, so the other handlers (which -// would need State) are simply never hit. -func settingsAPIWithConfig(t *testing.T, cfg *types.Config) humatest.TestAPI { - t.Helper() - - _, api := humatest.New(t, apiv2.Config()) - apiv2.Register(api, apiv2.Backend{Cfg: cfg}) - - return api -} - -func getSettings(t *testing.T, api humatest.TestAPI) apiv2.TailnetSettings { - t.Helper() - - resp := api.Get("/api/v2/tailnet/-/settings") - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var s apiv2.TailnetSettings - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &s)) - - return s -} - -// TestAPIv2SettingsComputedFields validates the GET mapping for each field that -// is computed from config — the branches the default-config roundtrip never -// exercises. Expectations live in struct fields, not name branches. -func TestAPIv2SettingsComputedFields(t *testing.T) { - tests := []struct { - name string - cfg types.Config - wantHTTPS bool - wantKeyDurationDays int - wantACLsExtManaged bool - }{ - { - name: "defaults", - cfg: types.Config{}, - }, - { - name: "https via cert path", - cfg: types.Config{TLS: types.TLSConfig{CertPath: "/x/cert.pem"}}, - wantHTTPS: true, - }, - { - name: "https via letsencrypt", - cfg: types.Config{ - TLS: types.TLSConfig{LetsEncrypt: types.LetsEncryptConfig{Hostname: "hs.example.com"}}, - }, - wantHTTPS: true, - }, - { - name: "key duration 7d", - cfg: types.Config{Node: types.NodeConfig{Expiry: 7 * 24 * time.Hour}}, - wantKeyDurationDays: 7, - }, - { - name: "key duration 90d", - cfg: types.Config{Node: types.NodeConfig{Expiry: 90 * 24 * time.Hour}}, - wantKeyDurationDays: 90, - }, - { - name: "key duration truncates", - cfg: types.Config{Node: types.NodeConfig{Expiry: 36 * time.Hour}}, - wantKeyDurationDays: 1, - }, - { - name: "acls externally managed in file mode", - cfg: types.Config{Policy: types.PolicyConfig{Mode: types.PolicyModeFile}}, - wantACLsExtManaged: true, - }, - { - name: "acls db mode", - cfg: types.Config{Policy: types.PolicyConfig{Mode: types.PolicyModeDB}}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := getSettings(t, settingsAPIWithConfig(t, &tt.cfg)) - - assert.Equal(t, tt.wantHTTPS, s.HTTPSEnabled) - assert.Equal(t, tt.wantKeyDurationDays, s.DevicesKeyDurationDays) - assert.Equal(t, tt.wantACLsExtManaged, s.ACLsExternallyManagedOn) - assert.Equal(t, "none", s.UsersRoleAllowedToJoinExternalTailnets) - }) - } -} - -// TestAPIv2SettingsConstantOffFields pins the hardcoded-off fields: -// even with every config knob set, they must not pick up signal. -func TestAPIv2SettingsConstantOffFields(t *testing.T) { - cfg := types.Config{ - TLS: types.TLSConfig{CertPath: "/x/cert.pem"}, - Node: types.NodeConfig{Expiry: 90 * 24 * time.Hour}, - Policy: types.PolicyConfig{Mode: types.PolicyModeFile}, - } - - s := getSettings(t, settingsAPIWithConfig(t, &cfg)) - - // Only the four computed fields reflect cfg; everything else stays off. - want := apiv2.TailnetSettings{ - ACLsExternallyManagedOn: true, - DevicesKeyDurationDays: 90, - HTTPSEnabled: true, - UsersRoleAllowedToJoinExternalTailnets: "none", - } - if diff := cmp.Diff(want, s); diff != "" { - t.Errorf("settings mismatch (-want +got):\n%s", diff) - } -} - -// TestAPIv2SettingsPatchUnsupported confirms writes are rejected and inert. -func TestAPIv2SettingsPatchUnsupported(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - patch := api.Patch("/api/v2/tailnet/-/settings", map[string]any{"devicesApprovalOn": true}) - assert.Equal(t, http.StatusNotImplemented, patch.Code) - assert.Contains(t, patch.Body.String(), `"message"`) - - // The rejected PATCH did not mutate anything. - assert.False(t, getSettings(t, api).DevicesApprovalOn) -} - -// TestAPIv2SettingsNonDefaultTailnet404 — the tailnet check runs before the -// 501, so a bad tailnet is 404 on both verbs. -func TestAPIv2SettingsNonDefaultTailnet404(t *testing.T) { - api := settingsAPIWithConfig(t, &types.Config{}) - - assert.Equal(t, http.StatusNotFound, api.Get("/api/v2/tailnet/example.com/settings").Code) - assert.Equal(t, http.StatusNotFound, - api.Patch("/api/v2/tailnet/example.com/settings", map[string]any{}).Code) -} diff --git a/hscontrol/apiv2_users_test.go b/hscontrol/apiv2_users_test.go deleted file mode 100644 index 38efd9a68..000000000 --- a/hscontrol/apiv2_users_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package hscontrol - -import ( - "encoding/json" - "net/http" - "strconv" - "testing" - - "github.com/danielgtaylor/huma/v2/humatest" - "github.com/google/go-cmp/cmp" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// userID stringifies a test user's id the way the API emits it. -func userID(u *types.User) string { - return strconv.FormatUint(uint64(u.ID), 10) -} - -// getUserByID GETs a user by id and decodes it. -func getUserByID(t *testing.T, api humatest.TestAPI, id string) apiv2.User { - t.Helper() - - resp := api.Get("/api/v2/users/" + id) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var user apiv2.User - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &user)) - - return user -} - -// listUsers GETs the user list (with an optional raw query string) and returns -// the decoded slice plus the raw body, so tests can pin the envelope shape. -func listUsers(t *testing.T, api humatest.TestAPI, query string) ([]apiv2.User, string) { - t.Helper() - - resp := api.Get("/api/v2/tailnet/-/users" + query) - require.Equalf(t, http.StatusOK, resp.Code, "body: %s", resp.Body) - - var env struct { - Users []apiv2.User `json:"users"` - } - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &env)) - - return env.Users, resp.Body.String() -} - -func containsUserID(users []apiv2.User, id string) bool { - for _, u := range users { - if u.ID == id { - return true - } - } - - return false -} - -func TestAPIv2User_Get(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - user := app.state.CreateUserForTest("golden") - - got := getUserByID(t, api, userID(user)) - - // Shape for a user with no devices: identity mapped, unmodelled fields fixed. - want := apiv2.User{ - ID: userID(user), - DisplayName: "golden", - LoginName: "golden", - TailnetID: "1", - Created: user.CreatedAt, - Type: "member", - Role: "member", - Status: "active", - } - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("user mismatch (-want +got):\n%s", diff) - } -} - -func TestAPIv2User_Get_DeviceCount(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - user := app.state.CreateUserForTest("owner") - - const wantDevices = 3 - for i := range wantDevices { - node := app.state.CreateRegisteredNodeForTest(user, "dut-"+strconv.Itoa(i)) - app.state.PutNodeInStoreForTest(*node) - } - - got := getUserByID(t, api, userID(user)) - assert.Equal(t, wantDevices, got.DeviceCount, "deviceCount aggregates the user's nodes") -} - -func TestAPIv2User_Get_NotFound(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - tests := []struct { - name string - id string - }{ - {name: "unknown numeric id", id: "999999"}, - {name: "non-numeric id", id: "not-a-number"}, - // The tagged-devices pseudo-user is never a real DB row. - {name: "tagged-devices pseudo-user", id: strconv.Itoa(types.TaggedDevicesUserID)}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp := api.Get("/api/v2/users/" + tt.id) - assert.Equal(t, http.StatusNotFound, resp.Code) - assert.Contains(t, resp.Body.String(), `"message"`, "Tailscale error shape") - }) - } -} - -func TestAPIv2User_List(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - u1 := app.state.CreateUserForTest("alice") - u2 := app.state.CreateUserForTest("bob") - - users, body := listUsers(t, api, "") - assert.Contains(t, body, `"users"`, "list is enveloped under users") - assert.True(t, containsUserID(users, userID(u1))) - assert.True(t, containsUserID(users, userID(u2))) - assert.False(t, containsUserID(users, strconv.Itoa(types.TaggedDevicesUserID)), - "tagged-devices pseudo-user is not listed") - - for _, u := range users { - assert.NotEmpty(t, u.ID, "every user has an id") - assert.NotEmpty(t, u.LoginName, "every user has a login name") - assert.Equal(t, "member", u.Type) - assert.Equal(t, "active", u.Status) - } -} - -func TestAPIv2User_List_Filters(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - app.state.CreateUserForTest("alice") - app.state.CreateUserForTest("bob") - - tests := []struct { - name string - query string - wantEmpty bool - }{ - {name: "no filter", query: "", wantEmpty: false}, - {name: "type member", query: "?type=member", wantEmpty: false}, - {name: "role member", query: "?role=member", wantEmpty: false}, - {name: "member and member", query: "?type=member&role=member", wantEmpty: false}, - {name: "type shared", query: "?type=shared", wantEmpty: true}, - {name: "role admin", query: "?role=admin", wantEmpty: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - users, body := listUsers(t, api, tt.query) - assert.Contains(t, body, `"users"`) - assert.NotContains(t, body, "null", "empty list marshals as [] not null") - - if tt.wantEmpty { - assert.Empty(t, users) - } else { - assert.NotEmpty(t, users) - } - }) - } -} - -func TestAPIv2User_List_BadTailnet(t *testing.T) { - app := createTestApp(t) - api := registerAPIV2(t, app) - - resp := api.Get("/api/v2/tailnet/example.com/users") - assert.Equal(t, http.StatusNotFound, resp.Code) - assert.Contains(t, resp.Body.String(), `"message"`) -} diff --git a/hscontrol/app.go b/hscontrol/app.go index fbbd6683e..30134ac61 100644 --- a/hscontrol/app.go +++ b/hscontrol/app.go @@ -24,8 +24,9 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/metrics" - apiv1 "github.com/juanfont/headscale/hscontrol/api/v1" - apiv2 "github.com/juanfont/headscale/hscontrol/api/v2" + grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/juanfont/headscale" + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/capver" "github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/derp" @@ -36,12 +37,22 @@ import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types/change" "github.com/juanfont/headscale/hscontrol/util" + zerolog "github.com/philip-bui/grpc-zerolog" "github.com/pkg/profile" + zl "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/sasha-s/go-deadlock" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" "tailscale.com/envknob" "tailscale.com/tailcfg" "tailscale.com/types/dnstype" @@ -73,6 +84,7 @@ func init() { } const ( + AuthPrefix = "Bearer " updateInterval = 5 * time.Second privateKeyFileMode = 0o600 headscaleDirPerm = 0o700 @@ -87,10 +99,6 @@ type Headscale struct { DERPServer *derpServer.DERPServer - // realIPMiddleware is nil when cfg.TrustedProxies is empty; the - // router skips the mount and r.RemoteAddr stays as the TCP peer. - realIPMiddleware func(http.Handler) http.Handler - // Things that generate changes extraRecordMan *dns.ExtraRecordsMan authProvider AuthProvider @@ -132,13 +140,6 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { state: s, } - if len(cfg.TrustedProxies) > 0 { - app.realIPMiddleware, err = trustedProxyRealIP(cfg.TrustedProxies) - if err != nil { - return nil, fmt.Errorf("building trusted_proxies middleware: %w", err) - } - } - // Initialize ephemeral garbage collector ephemeralGC := db.NewEphemeralGarbageCollector(func(ni types.NodeID) { node, ok := app.state.GetNodeByID(ni) @@ -192,15 +193,13 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { if cfg.PrefixV4 != nil { magicDNSDomains = append( magicDNSDomains, - util.GenerateIPv4DNSRootDomain(*cfg.PrefixV4)..., - ) + util.GenerateIPv4DNSRootDomain(*cfg.PrefixV4)...) } if cfg.PrefixV6 != nil { magicDNSDomains = append( magicDNSDomains, - util.GenerateIPv6DNSRootDomain(*cfg.PrefixV6)..., - ) + util.GenerateIPv6DNSRootDomain(*cfg.PrefixV6)...) } // we might have routes already from Split DNS @@ -209,19 +208,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { } for _, d := range magicDNSDomains { - // Empty non-nil slice rather than nil: tailcfg.DNSConfig.Clone - // and dns.Config.Clone in tailscale drop map entries whose - // value is nil (see tailscale.com/tailcfg/tailcfg_clone.go and - // tailscale.com/net/dns/dns_clone.go: `if sv == nil { continue }`). - // Sending nil here caused the client's wgengine LinkChange:major - // handler to clobber /etc/resolv.conf on every tunnel-IP rebind - // — the handler reapplies a Clone of lastDNSConfig and the magic - // DNS routes vanish, taking the resolver with them for ~6 min - // until the next route-changing netmap. Empty slice survives - // Clone and carries the same "resolve locally" semantics - // (tailscale.com/ipn/ipnlocal/node_backend.go:869 documents the - // empty-resolver Routes form for Issue 2706). - app.cfg.TailcfgDNSConfig.Routes[d.WithoutTrailingDot()] = []*dnstype.Resolver{} + app.cfg.TailcfgDNSConfig.Routes[d.WithoutTrailingDot()] = nil } } @@ -264,7 +251,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) { // Redirect to our TLS url. func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) { target := h.cfg.ServerURL + req.URL.RequestURI() - http.Redirect(w, req, target, http.StatusFound) //nolint:gosec // G710: target prefixed by trusted ServerURL + http.Redirect(w, req, target, http.StatusFound) } func (h *Headscale) scheduledTasks(ctx context.Context) { @@ -273,7 +260,7 @@ func (h *Headscale) scheduledTasks(ctx context.Context) { lastExpiryCheck := time.Unix(0, 0) - var derpTickerChan <-chan time.Time + derpTickerChan := make(<-chan time.Time) if h.cfg.DERP.AutoUpdate && h.cfg.DERP.UpdateFrequency != 0 { derpTicker := time.NewTicker(h.cfg.DERP.UpdateFrequency) @@ -285,6 +272,8 @@ func (h *Headscale) scheduledTasks(ctx context.Context) { var extraRecordsUpdate <-chan []tailcfg.DNSRecord if h.extraRecordMan != nil { extraRecordsUpdate = h.extraRecordMan.UpdateCh() + } else { + extraRecordsUpdate = make(chan []tailcfg.DNSRecord) } var ( @@ -308,46 +297,16 @@ func (h *Headscale) scheduledTasks(ctx context.Context) { Dur("interval", h.cfg.Node.Routes.HA.ProbeInterval). Dur("timeout", h.cfg.Node.Routes.HA.ProbeTimeout). Msg("HA subnet router health probing enabled") + } else { + haHealthChan = make(<-chan time.Time) } - var revokedKeyGCChan <-chan time.Time - - if h.cfg.PreAuthKeys.RevokedRetention > 0 { - revokedKeyTicker := time.NewTicker(time.Hour) - defer revokedKeyTicker.Stop() - - revokedKeyGCChan = revokedKeyTicker.C - } - - // OAuth access tokens are short-lived (1h) and re-minted on demand; reap - // expired rows hourly so the table stays bounded. - accessTokenTicker := time.NewTicker(time.Hour) - defer accessTokenTicker.Stop() - for { select { case <-ctx.Done(): log.Info().Caller().Msg("scheduled task worker is shutting down.") return - case <-revokedKeyGCChan: - cutoff := time.Now().Add(-h.cfg.PreAuthKeys.RevokedRetention) - - reaped, err := h.state.DestroyRevokedPreAuthKeysBefore(cutoff) - if err != nil { - log.Error().Err(err).Msg("reaping revoked pre-auth keys") - } else if reaped > 0 { - log.Info().Int("count", reaped).Msg("reaped revoked pre-auth keys") - } - - case <-accessTokenTicker.C: - reaped, err := h.state.DeleteExpiredAccessTokens(time.Now()) - if err != nil { - log.Error().Err(err).Msg("reaping expired oauth access tokens") - } else if reaped > 0 { - log.Debug().Int64("count", reaped).Msg("reaped expired oauth access tokens") - } - case <-expireTicker.C: var ( expiredNodeChanges []change.Change @@ -395,7 +354,7 @@ func (h *Headscale) scheduledTasks(ctx context.Context) { continue } - h.cfg.SetExtraRecords(records) + h.cfg.TailcfgDNSConfig.ExtraRecords = records h.Change(change.ExtraRecords()) @@ -405,6 +364,119 @@ func (h *Headscale) scheduledTasks(ctx context.Context) { } } +func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context, + req any, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, +) (any, error) { + // Check if the request is coming from the on-server client. + // This is not secure, but it is to maintain maintainability + // with the "legacy" database-based client + // It is also needed for grpc-gateway to be able to connect to + // the server + client, _ := peer.FromContext(ctx) + + log.Trace(). + Caller(). + Str("client_address", client.Addr.String()). + Msg("Client is trying to authenticate") + + meta, ok := metadata.FromIncomingContext(ctx) + if !ok { + return ctx, status.Errorf( + codes.InvalidArgument, + "retrieving metadata", + ) + } + + authHeader, ok := meta["authorization"] + if !ok { + return ctx, status.Errorf( + codes.Unauthenticated, + "authorization token not supplied", + ) + } + + token := authHeader[0] + + if !strings.HasPrefix(token, AuthPrefix) { + return ctx, status.Error( + codes.Unauthenticated, + `missing "Bearer " prefix in "Authorization" header`, + ) + } + + valid, err := h.state.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix)) + if err != nil { + return ctx, status.Error(codes.Internal, "validating token") + } + + if !valid { + log.Info(). + Str("client_address", client.Addr.String()). + Msg("invalid token") + + return ctx, status.Error(codes.Unauthenticated, "invalid token") + } + + return handler(ctx, req) +} + +func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func( + writer http.ResponseWriter, + req *http.Request, + ) { + log.Trace(). + Caller(). + Str("client_address", req.RemoteAddr). + Msg("HTTP authentication invoked") + + authHeader := req.Header.Get("Authorization") + + writeUnauthorized := func(statusCode int) { + writer.WriteHeader(statusCode) + + if _, err := writer.Write([]byte("Unauthorized")); err != nil { //nolint:noinlineerr + log.Error().Err(err).Msg("writing HTTP response failed") + } + } + + if !strings.HasPrefix(authHeader, AuthPrefix) { + log.Error(). + Caller(). + Str("client_address", req.RemoteAddr). + Msg(`missing "Bearer " prefix in "Authorization" header`) + writeUnauthorized(http.StatusUnauthorized) + + return + } + + valid, err := h.state.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix)) + if err != nil { + log.Info(). + Caller(). + Err(err). + Str("client_address", req.RemoteAddr). + Msg("failed to validate token") + writeUnauthorized(http.StatusUnauthorized) + + return + } + + if !valid { + log.Info(). + Str("client_address", req.RemoteAddr). + Msg("invalid token") + writeUnauthorized(http.StatusUnauthorized) + + return + } + + next.ServeHTTP(writer, req) + }) +} + // ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear // and will remove it if it is not. func (h *Headscale) ensureUnixSocketIsAbsent() error { @@ -430,18 +502,7 @@ func securityHeaders(next http.Handler) http.Handler { }) } -// serveHumaMux dispatches to a Huma mux mounted under the outer chi router. -// Huma registers operations at absolute paths (/api/v1/...), so chi's route -// context must be cleared for the inner mux to re-match against the original URL. -func serveHumaMux(mux http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - mux.ServeHTTP(w, req.WithContext( - context.WithValue(req.Context(), chi.RouteCtxKey, nil), - )) - } -} - -func (h *Headscale) createRouter(apiV1Mux, apiV2Mux http.Handler) *chi.Mux { +func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *chi.Mux { r := chi.NewRouter() r.Use(metrics.Collector(metrics.CollectorOpts{ Host: false, @@ -451,20 +512,11 @@ func (h *Headscale) createRouter(apiV1Mux, apiV2Mux http.Handler) *chi.Mux { }, })) r.Use(middleware.RequestID) - - if h.realIPMiddleware != nil { - r.Use(h.realIPMiddleware) - } - + r.Use(middleware.RealIP) r.Use(middleware.RequestLogger(&zerologRequestLogger{})) r.Use(middleware.Recoverer) r.Use(securityHeaders) - // TS2021 accepts both the native client's HTTP POST upgrade and the - // browser/WASM client's WebSocket GET upgrade; NoiseUpgradeHandler - // dispatches on the Upgrade header, not the method. Registering GET as - // well keeps the router from rejecting the WebSocket handshake with 405. - r.Get(ts2021UpgradePath, h.NoiseUpgradeHandler) r.Post(ts2021UpgradePath, h.NoiseUpgradeHandler) r.Get("/robots.txt", h.RobotsHandler) @@ -483,6 +535,10 @@ func (h *Headscale) createRouter(apiV1Mux, apiV2Mux http.Handler) *chi.Mux { r.Get("/apple/{platform}", h.ApplePlatformConfig) r.Get("/windows", h.WindowsConfigMessage) + // TODO(kristoffer): move swagger into a package + r.Get("/swagger", headscale.SwaggerUI) + r.Get("/swagger/v1/openapiv2.json", headscale.SwaggerAPIv1) + r.Post("/verify", h.VerifyHandler) if h.cfg.DERP.ServerEnabled { @@ -492,16 +548,12 @@ func (h *Headscale) createRouter(apiV1Mux, apiV2Mux http.Handler) *chi.Mux { r.HandleFunc("/bootstrap-dns", derpServer.DERPBootstrapDNSHandler(h.state.DERPMap())) } - // Auth is enforced inside each Huma mux per-operation, so the whole API - // mounts as one handler per version: operations need an API key while the - // OpenAPI document and docs UI stay public. v1 is the headscale-native admin - // API; v2 is Headscale's v2 API, which ports some endpoints from Tailscale. r.Route("/api", func(r chi.Router) { - r.Handle("/v1/*", serveHumaMux(apiV1Mux)) - r.Handle("/v2/*", serveHumaMux(apiV2Mux)) + r.Use(h.httpAuthenticationMiddleware) + r.HandleFunc("/v1/*", grpcMux.ServeHTTP) }) // Ping response endpoint: receives HEAD from clients responding - // to a [tailcfg.PingRequest]. The unguessable ping ID serves as authentication. + // to a PingRequest. The unguessable ping ID serves as authentication. r.Head("/machine/ping-response", h.PingResponseHandler) r.Get("/favicon.ico", FaviconHandler) @@ -510,7 +562,7 @@ func (h *Headscale) createRouter(apiV1Mux, apiV2Mux http.Handler) *chi.Mux { return r } -// Serve launches the HTTP servers that run Headscale and its API. +// Serve launches the HTTP and gRPC server service Headscale and the API. // //nolint:gocyclo // complex server startup function func (h *Headscale) Serve() error { @@ -588,7 +640,7 @@ func (h *Headscale) Serve() error { return fmt.Errorf("setting up extrarecord manager: %w", err) } - h.cfg.SetExtraRecords(h.extraRecordMan.Records()) + h.cfg.TailcfgDNSConfig.ExtraRecords = h.extraRecordMan.Records() go h.extraRecordMan.Run() defer h.extraRecordMan.Close() @@ -601,6 +653,12 @@ func (h *Headscale) Serve() error { go h.scheduledTasks(scheduleCtx) + if zl.GlobalLevel() == zl.TraceLevel { + zerolog.RespLog = true + } else { + zerolog.RespLog = false + } + // Prepare group for running listeners errorGroup := new(errgroup.Group) @@ -628,47 +686,45 @@ func (h *Headscale) Serve() error { socketListener, err := new(net.ListenConfig).Listen(context.Background(), "unix", h.cfg.UnixSocket) if err != nil { - return fmt.Errorf("setting up socket: %w", err) + return fmt.Errorf("setting up gRPC socket: %w", err) } // Change socket permissions if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil { //nolint:noinlineerr - return fmt.Errorf("changing socket permission: %w", err) + return fmt.Errorf("changing gRPC socket permission: %w", err) } - // The Huma v1 API mux matches full /api/v1/... paths and is shared by - // the local unix socket (served without authentication, local trust) - // and the remote TCP router (served behind the API-key middleware). - humaMux, _ := apiv1.Handler(apiv1.Backend{ - State: h.state, - Change: h.Change, - Cfg: h.cfg, - }) + grpcGatewayMux := grpcRuntime.NewServeMux() - // The Headscale v2 API. Served behind Basic/Bearer auth on the remote - // listener, and over the local unix socket (local trust) so the CLI can - // manage OAuth clients through the same v2 keys handler the Tailscale - // ecosystem uses. - humaV2Mux, _ := apiv2.Handler(apiv2.Backend{ - State: h.state, - Change: h.Change, - Cfg: h.cfg, - }) - - // Serve both Huma APIs over the unix socket without TLS or auth: socket - // access implies trust. WithLocalTrust marks these requests so each API's - // security middleware skips the credential check. v2 paths route to the v2 - // mux; everything else (the v1 paths) to v1. - socketHandler := http.NewServeMux() - socketHandler.Handle("/api/v2/", apiv2.WithLocalTrust(humaV2Mux)) - socketHandler.Handle("/", apiv1.WithLocalTrust(humaMux)) - - socketServer := &http.Server{ - Handler: socketHandler, - ReadTimeout: types.HTTPTimeout, + // Make the grpc-gateway connect to grpc over socket + grpcGatewayConn, err := grpc.Dial( //nolint:staticcheck // SA1019: deprecated but supported in 1.x + h.cfg.UnixSocket, + []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(util.GrpcSocketDialer), + }..., + ) + if err != nil { + return fmt.Errorf("setting up gRPC gateway via socket: %w", err) } - errorGroup.Go(func() error { return socketServer.Serve(socketListener) }) + // Connect to the gRPC server over localhost to skip + // the authentication. + err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn) + if err != nil { + return fmt.Errorf("registering Headscale API service to gRPC: %w", err) + } + + // Start the local gRPC server without TLS and without authentication + grpcSocket := grpc.NewServer( + // Uncomment to debug grpc communication. + // zerolog.UnaryInterceptor(), + ) + + v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h)) + reflection.Register(grpcSocket) + + errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) }) // // @@ -680,13 +736,64 @@ func (h *Headscale) Serve() error { return fmt.Errorf("configuring TLS settings: %w", err) } + // + // + // gRPC setup + // + + // We are sadly not able to run gRPC and HTTPS (2.0) on the same + // port because the connection mux does not support matching them + // since they are so similar. There is multiple issues open and we + // can revisit this if changes: + // https://github.com/soheilhy/cmux/issues/68 + // https://github.com/soheilhy/cmux/issues/91 + + var ( + grpcServer *grpc.Server + grpcListener net.Listener + ) + + if tlsConfig != nil || h.cfg.GRPCAllowInsecure { + log.Info().Msgf("enabling remote gRPC at %s", h.cfg.GRPCAddr) + + grpcOptions := []grpc.ServerOption{ + grpc.ChainUnaryInterceptor( + h.grpcAuthenticationInterceptor, + // Uncomment to debug grpc communication. + // zerolog.NewUnaryServerInterceptor(), + ), + } + + if tlsConfig != nil { + grpcOptions = append(grpcOptions, + grpc.Creds(credentials.NewTLS(tlsConfig)), + ) + } else { + log.Warn().Msg("gRPC is running without security") + } + + grpcServer = grpc.NewServer(grpcOptions...) + + v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) + + grpcListener, err = new(net.ListenConfig).Listen(context.Background(), "tcp", h.cfg.GRPCAddr) + if err != nil { + return fmt.Errorf("binding to TCP address: %w", err) + } + + errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) }) + + log.Info(). + Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr) + } + // // // HTTP setup // // This is the regular router that we expose // over our main Addr - router := h.createRouter(humaMux, humaV2Mux) + router := h.createRouter(grpcGatewayMux) httpServer := &http.Server{ Addr: h.cfg.Addr, @@ -827,10 +934,13 @@ func (h *Headscale) Serve() error { info("waiting for netmap stream to close") h.clientStreamsOpen.Wait() - info("shutting down api server (socket)") + info("shutting down grpc server (socket)") + grpcSocket.GracefulStop() - if err := socketServer.Shutdown(shutdownCtx); err != nil { //nolint:noinlineerr - log.Error().Err(err).Msg("failed to shutdown socket server") + if grpcServer != nil { + info("shutting down grpc server (external)") + grpcServer.GracefulStop() + grpcListener.Close() } if tailsqlContext != nil { @@ -846,6 +956,7 @@ func (h *Headscale) Serve() error { } httpListener.Close() + grpcGatewayConn.Close() // Stop listening (and unlink the socket if unix type): info("closing socket listener") @@ -877,14 +988,14 @@ func (h *Headscale) Serve() error { } func (h *Headscale) getTLSSettings() (*tls.Config, error) { - tlsEnabled := h.cfg.TLS.LetsEncrypt.Hostname != "" || h.cfg.TLS.CertPath != "" - if tlsEnabled && !strings.HasPrefix(h.cfg.ServerURL, "https://") { - log.Warn().Msg("listening with TLS but ServerURL does not start with https://") - } else if !tlsEnabled && !strings.HasPrefix(h.cfg.ServerURL, "http://") { - log.Warn().Msg("listening without TLS but ServerURL does not start with http://") - } + var err error if h.cfg.TLS.LetsEncrypt.Hostname != "" { + if !strings.HasPrefix(h.cfg.ServerURL, "https://") { + log.Warn(). + Msg("Listening with TLS but ServerURL does not start with https://") + } + certManager := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(h.cfg.TLS.LetsEncrypt.Hostname), @@ -930,26 +1041,27 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { default: return nil, errUnsupportedLetsEncryptChallengeType } - } + } else if h.cfg.TLS.CertPath == "" { + if !strings.HasPrefix(h.cfg.ServerURL, "http://") { + log.Warn().Msg("listening without TLS but ServerURL does not start with http://") + } - if h.cfg.TLS.CertPath == "" { - return nil, nil //nolint:nilnil // intentional: no TLS config when neither LetsEncrypt nor a cert path is set - } - - tlsConfig := &tls.Config{ - NextProtos: []string{"http/1.1"}, - Certificates: make([]tls.Certificate, 1), - MinVersion: tls.VersionTLS12, - } - - cert, err := tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath) - if err != nil { return nil, err + } else { + if !strings.HasPrefix(h.cfg.ServerURL, "https://") { + log.Warn().Msg("listening with TLS but ServerURL does not start with https://") + } + + tlsConfig := &tls.Config{ + NextProtos: []string{"http/1.1"}, + Certificates: make([]tls.Certificate, 1), + MinVersion: tls.VersionTLS12, + } + + tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath) + + return tlsConfig, err } - - tlsConfig.Certificates[0] = cert - - return tlsConfig, nil } func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) { @@ -1005,23 +1117,11 @@ func (h *Headscale) Change(cs ...change.Change) { h.mapBatcher.AddWork(cs...) } -// HTTPHandler returns an [http.Handler] for the [Headscale] control server. +// HTTPHandler returns an http.Handler for the Headscale control server. // The handler serves the Tailscale control protocol including the /key // endpoint and /ts2021 Noise upgrade path. func (h *Headscale) HTTPHandler() http.Handler { - humaMux, _ := apiv1.Handler(apiv1.Backend{ - State: h.state, - Change: h.Change, - Cfg: h.cfg, - }) - - humaV2Mux, _ := apiv2.Handler(apiv2.Backend{ - State: h.state, - Change: h.Change, - Cfg: h.cfg, - }) - - return h.createRouter(humaMux, humaV2Mux) + return h.createRouter(grpcRuntime.NewServeMux()) } // NoisePublicKey returns the server's Noise protocol public key. @@ -1097,7 +1197,7 @@ func (l *acmeLogger) RoundTrip(req *http.Request) (*http.Response, error) { return resp, nil } -// [zerologRequestLogger] implements chi's [middleware.LogFormatter] +// zerologRequestLogger implements chi's middleware.LogFormatter // to route HTTP request logs through zerolog. type zerologRequestLogger struct{} diff --git a/hscontrol/auth.go b/hscontrol/auth.go index 81e620bfd..853df605e 100644 --- a/hscontrol/auth.go +++ b/hscontrol/auth.go @@ -23,18 +23,6 @@ type AuthProvider interface { AuthURL(authID types.AuthID) string } -// machineKeyMismatch fails closed when a node looked up by NodeKey was started -// in a Noise session with a different machine key. Without this anyone holding a -// target's NodeKey could open a session with a throwaway machine key and act on -// the owner's node. Returns a 401 [HTTPError] on mismatch, nil otherwise. -func machineKeyMismatch(node types.NodeView, machineKey key.MachinePublic) error { - if node.MachineKey() != machineKey { - return NewHTTPError(http.StatusUnauthorized, "node exists with a different machine key", nil) - } - - return nil -} - func (h *Headscale) handleRegister( ctx context.Context, req tailcfg.RegisterRequest, @@ -85,16 +73,20 @@ func (h *Headscale) handleRegister( // the Noise session's machine key matches the cached node. // Without this check anyone holding a target's NodeKey could // open a Noise session with a throwaway machine key and read - // the owner's User/Login back through [nodeToRegisterResponse]. - // [Headscale.handleLogout] enforces the same check on its own path. - err := machineKeyMismatch(node, machineKey) - if err != nil { - return nil, err + // the owner's User/Login back through nodeToRegisterResponse. + // handleLogout enforces the same check on its own path. + if node.MachineKey() != machineKey { + return nil, NewHTTPError( + http.StatusUnauthorized, + "node exists with a different machine key", + nil, + ) } - // When tailscaled restarts, it sends [tailcfg.RegisterRequest] with Auth=nil and Expiry=zero. + // When tailscaled restarts, it sends RegisterRequest with Auth=nil and Expiry=zero. // Return the current node state without modification. - if req.Expiry.IsZero() && !node.IsExpired() { + // See: https://github.com/juanfont/headscale/issues/2862 + if req.Expiry.IsZero() && node.Expiry().Valid() && !node.IsExpired() { return nodeToRegisterResponse(node), nil } @@ -162,9 +154,8 @@ func (h *Headscale) handleLogout( // Fail closed if it looks like this is an attempt to modify a node where // the node key and the machine key the noise session was started with does // not align. - err := machineKeyMismatch(node, machineKey) - if err != nil { - return nil, err + if node.MachineKey() != machineKey { + return nil, NewHTTPError(http.StatusUnauthorized, "node exist with different machine key", nil) } // Note: We do NOT return early if req.Auth is set, because Tailscale clients @@ -201,7 +192,7 @@ func (h *Headscale) handleLogout( } // If the request expiry is in the past, we consider it a logout. - // Zero expiry is handled in [Headscale.handleRegister] before calling this function. + // Zero expiry is handled in handleRegister() before calling this function. if req.Expiry.Before(time.Now()) { log.Debug(). EmbedObject(node). @@ -235,16 +226,7 @@ func (h *Headscale) handleLogout( // Update the internal state with the nodes new expiry, meaning it is // logged out. - // - // Clamp the client-supplied value to now: Tailscale sends the - // sentinel time.Unix(123, 0) on logout (controlclient/direct.go), - // and storing it verbatim propagates a 1970 KeyExpiry to every - // peer's netmap. Semantically identical (expired as of now), but - // sane in logs, debug dumps, and peer netmaps. expiry := req.Expiry - if now := time.Now(); expiry.Before(now) { - expiry = now - } updatedNode, c, err := h.state.SetNodeExpiry(node.ID(), &expiry) if err != nil { @@ -272,7 +254,7 @@ func nodeToRegisterResponse(node types.NodeView) *tailcfg.RegisterResponse { MachineAuthorized: true, } - // For tagged nodes, use the [types.TaggedDevices] special user + // For tagged nodes, use the TaggedDevices special user // For user-owned nodes, include User and Login information from the actual user if node.IsTagged() { resp.User = types.TaggedDevices.View().TailscaleUser() @@ -321,8 +303,8 @@ func (h *Headscale) waitForFollowup( } // reqToNewRegisterResponse refreshes the registration flow by creating a new -// registration ID and returning the corresponding [tailcfg.RegisterResponse.AuthURL] -// so the client can restart the authentication process. +// registration ID and returning the corresponding AuthURL so the client can +// restart the authentication process. func (h *Headscale) reqToNewRegisterResponse( req tailcfg.RegisterRequest, machineKey key.MachinePublic, @@ -344,8 +326,8 @@ func (h *Headscale) reqToNewRegisterResponse( }, nil } -// registrationDataFromRequest builds the [types.RegistrationData] payload stored -// in the auth cache for a pending registration. The original [tailcfg.Hostinfo] is +// registrationDataFromRequest builds the RegistrationData payload stored +// in the auth cache for a pending registration. The original Hostinfo is // retained so that consumers (auth callback, observability) see the // fields the client originally announced; the bounded-LRU cap on the // cache is what bounds the unauthenticated cache-fill DoS surface. diff --git a/hscontrol/auth_tags_test.go b/hscontrol/auth_tags_test.go index 3cea0cb0c..1c761b9bd 100644 --- a/hscontrol/auth_tags_test.go +++ b/hscontrol/auth_tags_test.go @@ -1,7 +1,6 @@ package hscontrol import ( - "context" "testing" "time" @@ -60,7 +59,7 @@ func createTestAppWithNodeExpiry(t *testing.T, nodeExpiry time.Duration) *Headsc // a tagged node with: // - Tags from the PreAuthKey // - Nil UserID (tagged nodes are owned by tags, not a user) -// - [types.Node.IsTagged] returns true. +// - IsTagged() returns true. func TestTaggedPreAuthKeyCreatesTaggedNode(t *testing.T) { app := createTestApp(t) @@ -113,7 +112,7 @@ func TestTaggedPreAuthKeyCreatesTaggedNode(t *testing.T) { // authentication. This is critical for the container restart scenario (#2830). // // NOTE: This test verifies that re-authentication preserves the node's current tags -// without testing tag modification via [state.State.SetNodeTags] (which requires ACL policy setup). +// without testing tag modification via SetNodeTags (which requires ACL policy setup). func TestReAuthDoesNotReapplyTags(t *testing.T) { app := createTestApp(t) @@ -179,8 +178,9 @@ func TestReAuthDoesNotReapplyTags(t *testing.T) { assert.Equal(t, 1, allNodes.Len(), "Should have exactly one node") } -// NOTE: Converting user-owned nodes to tagged nodes while preserving UserID -// is covered by the SetTags API tests, which properly handle ACL policy setup. +// NOTE: TestSetTagsOnUserOwnedNode functionality is covered by gRPC tests in grpcv1_test.go +// which properly handle ACL policy setup. The test verifies that SetTags can convert +// user-owned nodes to tagged nodes while preserving UserID. // TestCannotRemoveAllTags tests that attempting to remove all tags from a // tagged node fails with ErrCannotRemoveAllTags. Once a node is tagged, @@ -669,151 +669,10 @@ func TestTaggedNodeReauthPreservesDisabledExpiry(t *testing.T) { "Tagged node should have expiry PRESERVED as disabled after re-auth") } -// TestTaggedNodeRestartPreservesNilExpiry tests that a tagged node whose -// tailscaled restarts (sending Auth=nil, Expiry=zero) keeps its nil expiry. -// -// The handleRegister guard required node.Expiry().Valid(), false for the -// nil expiry tagged nodes are created with. The request fell through to -// handleLogout, which wrote &time.Time{} over the original nil and flipped -// the API representation from null to "0001-01-01T00:00:00Z". -func TestTaggedNodeRestartPreservesNilExpiry(t *testing.T) { - app := createTestApp(t) - - user := app.state.CreateUserForTest("tag-restart") - tags := []string{"tag:agent"} - - pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, tags) - require.NoError(t, err) - - machineKey := key.NewMachine() - nodeKey := key.NewNode() - - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{ - AuthKey: pak.Key, - }, - NodeKey: nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{ - Hostname: "tagged-restart-test", - }, - } - - resp, err := app.handleRegisterWithAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.True(t, resp.MachineAuthorized) - - node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) - require.True(t, found) - require.True(t, node.IsTagged()) - require.False(t, node.Expiry().Valid(), "tagged node should have nil expiry after registration") - require.False(t, node.IsExpired(), "tagged node with nil expiry should not be expired") - - // tailscaled restart: RegisterRequest with Auth=nil and Expiry=time.Time{} - // (the Go zero value) is what the client sends when it restarts with - // persisted state. - restartReq := tailcfg.RegisterRequest{ - Auth: nil, - NodeKey: nodeKey.Public(), - Expiry: time.Time{}, - } - - restartResp, err := app.handleRegister(context.Background(), restartReq, machineKey.Public()) - require.NoError(t, err) - - require.True(t, restartResp.MachineAuthorized, - "restart should not require re-authorization") - require.False(t, restartResp.NodeKeyExpired, - "restart should not mark node key as expired") - require.Equal(t, types.TaggedDevices.View().TailscaleUser(), restartResp.User, - "response should identify node as tagged device") - - nodeAfterRestart, found := app.state.GetNodeByNodeKey(nodeKey.Public()) - require.True(t, found) - - assert.True(t, nodeAfterRestart.IsTagged(), "node should still be tagged") - assert.False(t, nodeAfterRestart.IsExpired(), "node should not be expired after restart") - assert.False(t, nodeAfterRestart.Expiry().Valid(), - "tagged node expiry must remain nil (not zero-time) after restart") - - var dbNode types.Node - require.NoError(t, - app.state.DB().DB.First(&dbNode, nodeAfterRestart.ID().Uint64()).Error) - assert.Nil(t, dbNode.Expiry, - "database expiry column must be NULL after restart, not a pointer to zero-time") -} - -// TestUntaggedNodeRestartPreservesNilExpiry tests that an untagged node -// registered against a preauth key with no default node.expiry keeps its -// nil expiry when tailscaled restarts. Same root cause as the tagged -// variant: the dropped node.Expiry().Valid() check covers any nil-expiry -// node, regardless of ownership. -func TestUntaggedNodeRestartPreservesNilExpiry(t *testing.T) { - app := createTestAppWithNodeExpiry(t, 0) - - user := app.state.CreateUserForTest("untagged-restart") - - pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - nodeKey := key.NewNode() - - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{ - AuthKey: pak.Key, - }, - NodeKey: nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{ - Hostname: "untagged-restart-test", - }, - Expiry: time.Time{}, - } - - resp, err := app.handleRegisterWithAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.True(t, resp.MachineAuthorized) - - node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) - require.True(t, found) - require.False(t, node.IsTagged(), "node should be user-owned, not tagged") - require.False(t, node.Expiry().Valid(), - "untagged node with no default expiry should have nil expiry after registration") - require.False(t, node.IsExpired()) - - restartReq := tailcfg.RegisterRequest{ - Auth: nil, - NodeKey: nodeKey.Public(), - Expiry: time.Time{}, - } - - restartResp, err := app.handleRegister(context.Background(), restartReq, machineKey.Public()) - require.NoError(t, err) - - require.True(t, restartResp.MachineAuthorized, - "restart should not require re-authorization") - require.False(t, restartResp.NodeKeyExpired, - "restart should not mark node key as expired") - - nodeAfterRestart, found := app.state.GetNodeByNodeKey(nodeKey.Public()) - require.True(t, found) - - assert.False(t, nodeAfterRestart.IsTagged(), "node should still be user-owned") - assert.False(t, nodeAfterRestart.IsExpired(), "node should not be expired after restart") - assert.False(t, nodeAfterRestart.Expiry().Valid(), - "untagged node expiry must remain nil (not zero-time) after restart") - - var dbNode types.Node - require.NoError(t, - app.state.DB().DB.First(&dbNode, nodeAfterRestart.ID().Uint64()).Error) - assert.Nil(t, dbNode.Expiry, - "database expiry column must be NULL after restart, not a pointer to zero-time "+ - "(this is what `sqlite3 ... 'select expiry from nodes'` sees)") -} - // TestExpiryDuringPersonalToTaggedConversion tests that when a personal node // is converted to tagged via reauth with RequestTags, the expiry is cleared to nil. -// Previously expiry was NOT cleared because expiry handling ran -// BEFORE [state.State.processReauthTags]. +// BUG #3048: Previously expiry was NOT cleared because expiry handling ran +// BEFORE processReauthTags. func TestExpiryDuringPersonalToTaggedConversion(t *testing.T) { app := createTestApp(t) user := app.state.CreateUserForTest("expiry-test-user") @@ -885,8 +744,8 @@ func TestExpiryDuringPersonalToTaggedConversion(t *testing.T) { // TestExpiryDuringTaggedToPersonalConversion tests that when a tagged node // is converted to personal via reauth with empty RequestTags, expiry is set // from the client request. -// Previously expiry was NOT set because expiry handling ran -// BEFORE [state.State.processReauthTags] (node was still tagged at check time). +// BUG #3048: Previously expiry was NOT set because expiry handling ran +// BEFORE processReauthTags (node was still tagged at check time). func TestExpiryDuringTaggedToPersonalConversion(t *testing.T) { app := createTestApp(t) user := app.state.CreateUserForTest("expiry-test-user2") @@ -1007,24 +866,16 @@ func TestReAuthWithDifferentMachineKey(t *testing.T) { Expiry: time.Now().Add(24 * time.Hour), } - // A NodeKey is bound 1:1 to a MachineKey (getAndValidateNode enforces - // this at poll time). A different machine claiming an existing NodeKey is - // a hijack: it would poison the NodeStore NodeKey index so the original - // node fails the poll-time MachineKey check and is denied service. - // Registration now rejects it (see f8f08cf7). Real Tailscale clients - // never reuse a NodeKey across machine keys, so no legitimate flow is - // affected. - _, err = app.handleRegisterWithAuthKey(regReq2, machineKey2.Public()) - require.Error(t, err, - "a different machine claiming an existing NodeKey must be rejected") + resp2, err := app.handleRegisterWithAuthKey(regReq2, machineKey2.Public()) + require.NoError(t, err) + require.True(t, resp2.MachineAuthorized) - // The original node is unaffected: still present, tagged, same identity. + // Verify the node still exists and has tags + // Note: Depending on implementation, this might be the same node or a new node node2, found := app.state.GetNodeByNodeKey(nodeKey.Public()) require.True(t, found) assert.True(t, node2.IsTagged()) assert.ElementsMatch(t, tags, node2.Tags().AsSlice()) - assert.Equal(t, node1.ID(), node2.ID(), - "original node must survive; the hijacking registration was rejected") } // TestUntaggedAuthKeyZeroExpiryGetsDefault tests that when node.expiry is configured @@ -1152,7 +1003,7 @@ func TestNodeExpiryZeroDisablesDefault(t *testing.T) { assert.False(t, node.IsExpired(), "node should not be expired") // With node.expiry=0 and zero client expiry, the node gets a zero expiry - // which [types.Node.IsExpired] treats as "never expires" — backwards compatible. + // which IsExpired() treats as "never expires" — backwards compatible. if node.Expiry().Valid() { assert.True(t, node.Expiry().Get().IsZero(), "with node.expiry=0 and zero client expiry, expiry should be zero time") @@ -1273,11 +1124,11 @@ func TestReregistrationAppliesDefaultExpiry(t *testing.T) { // re-registers with zero client expiry and node.expiry is disabled (0), // the node's expiry stays nil rather than being set to a pointer to zero // time. Regression test for the else branch introduced in commit 6337a3db -// which assigned `®Req.Expiry` (pointer to [time.Time]{}) instead of nil, +// which assigned `®Req.Expiry` (pointer to time.Time{}) instead of nil, // causing the database row to hold `0001-01-01 00:00:00` instead of NULL. // // The same !regReq.Expiry.IsZero() gate at state.go:2221-2228 is shared by -// the tags-only PreAuthKey path ([state.State.createAndSaveNewNode] also receives nil +// the tags-only PreAuthKey path (createAndSaveNewNode also receives nil // when the client sends zero expiry), so this regression is covered for // tagged nodes by inspection. func TestReregistrationZeroExpiryStaysNil(t *testing.T) { diff --git a/hscontrol/auth_test.go b/hscontrol/auth_test.go index ed294f3ca..5e13a4d71 100644 --- a/hscontrol/auth_test.go +++ b/hscontrol/auth_test.go @@ -31,7 +31,7 @@ type interactiveStep struct { stepType string // stepTypeInitialRequest, stepTypeAuthCompletion, or stepTypeFollowupRequest expectAuthURL bool expectCacheEntry bool - callAuthPath bool // Real call to [state.State.HandleNodeFromAuthPath], not mocked + callAuthPath bool // Real call to HandleNodeFromAuthPath, not mocked } //nolint:gocyclo // comprehensive test function with many scenarios @@ -140,7 +140,7 @@ func TestAuthenticationFlows(t *testing.T) { return "", err } - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore require.EventuallyWithT(t, func(c *assert.CollectT) { _, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(c, found, "node should be available in NodeStore") @@ -209,7 +209,7 @@ func TestAuthenticationFlows(t *testing.T) { return "", err } - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore require.EventuallyWithT(t, func(c *assert.CollectT) { _, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(c, found, "node should be available in NodeStore") @@ -409,7 +409,7 @@ func TestAuthenticationFlows(t *testing.T) { t.Logf("Setup registered node: %+v", resp) - // Wait for node to be available in [state.NodeStore] with debug info + // Wait for node to be available in NodeStore with debug info var attemptCount int require.EventuallyWithT(t, func(c *assert.CollectT) { @@ -470,7 +470,7 @@ func TestAuthenticationFlows(t *testing.T) { return "", err } - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore require.EventuallyWithT(t, func(c *assert.CollectT) { _, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(c, found, "node should be available in NodeStore") @@ -520,7 +520,7 @@ func TestAuthenticationFlows(t *testing.T) { return "", err } - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore require.EventuallyWithT(t, func(c *assert.CollectT) { _, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(c, found, "node should be available in NodeStore") @@ -637,7 +637,7 @@ func TestAuthenticationFlows(t *testing.T) { return "", err } - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore require.EventuallyWithT(t, func(c *assert.CollectT) { _, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(c, found, "node should be available in NodeStore") @@ -687,7 +687,7 @@ func TestAuthenticationFlows(t *testing.T) { app.state.SetAuthCacheEntry(regID, nodeToRegister) // Simulate successful registration - // [Headscale.handleRegister] will receive the value when it starts waiting + // handleRegister will receive the value when it starts waiting go func() { user := app.state.CreateUserForTest("followup-user") @@ -826,7 +826,7 @@ func TestAuthenticationFlows(t *testing.T) { }, // TEST: Nil hostinfo is handled with defensive code // WHAT: Tests that nil hostinfo in register request is handled gracefully - // INPUT: Register request with [tailcfg.Hostinfo] field set to nil + // INPUT: Register request with Hostinfo field set to nil // EXPECTED: Node registers successfully with generated hostname starting with "node-" // WHY: Defensive code prevents nil pointer panics; creates valid default hostinfo { @@ -856,7 +856,7 @@ func TestAuthenticationFlows(t *testing.T) { validate: func(t *testing.T, resp *tailcfg.RegisterResponse, app *Headscale) { //nolint:thelper //nolint:thelper assert.True(t, resp.MachineAuthorized) - // With nil [tailcfg.Hostinfo] the raw hostname stays empty and GivenName + // With nil Hostinfo the raw hostname stays empty and GivenName // falls back to the literal "node" per the SaaS spec. node, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) assert.True(t, found) @@ -954,7 +954,7 @@ func TestAuthenticationFlows(t *testing.T) { // TEST: PreAuthKey registration rejects client-provided RequestTags // WHAT: Tests that PreAuthKey registrations cannot use client-provided tags - // INPUT: PreAuthKey registration with [tailcfg.Hostinfo.RequestTags] set + // INPUT: PreAuthKey registration with RequestTags in Hostinfo // EXPECTED: Registration fails with "requested tags [...] are invalid or not permitted" error // WHY: PreAuthKey nodes get their tags from the key itself, not from client requests { @@ -1240,7 +1240,7 @@ func TestAuthenticationFlows(t *testing.T) { // TEST: Zero-time expiry is handled correctly // WHAT: Tests registration with expiry set to zero time value - // INPUT: Register request with Expiry set to [time.Time]{} (zero value) + // INPUT: Register request with Expiry set to time.Time{} (zero value) // EXPECTED: Node registers successfully; zero time treated as no expiry // WHY: Zero time is valid Go default; should be handled gracefully { @@ -1280,7 +1280,7 @@ func TestAuthenticationFlows(t *testing.T) { }, // TEST: Malformed hostinfo with very long hostname is truncated // WHAT: Tests that excessively long hostname is truncated to DNS label limit - // INPUT: [tailcfg.Hostinfo] with 110-character hostname (exceeds 63-char DNS limit) + // INPUT: Hostinfo with 110-character hostname (exceeds 63-char DNS limit) // EXPECTED: Node registers successfully; hostname truncated to 63 characters // WHY: Defensive code enforces DNS label limit (RFC 1123); prevents errors { @@ -1699,7 +1699,7 @@ func TestAuthenticationFlows(t *testing.T) { assert.False(t, resp.NodeKeyExpired) // Verify NEW node was created for user2 - node2, found := app.state.GetNodesByMachineKeyAllUsers(machineKey1.Public())[types.UserID(2)] + node2, found := app.state.GetNodeByMachineKey(machineKey1.Public(), types.UserID(2)) require.True(t, found, "new node should exist for user2") assert.Equal(t, uint(2), node2.UserID().Get(), "new node should belong to user2") @@ -1707,7 +1707,7 @@ func TestAuthenticationFlows(t *testing.T) { assert.Equal(t, "user2-context", user.Name(), "new node should show user2 username") // Verify original node still exists for user1 - node1, found := app.state.GetNodesByMachineKeyAllUsers(machineKey1.Public())[types.UserID(1)] + node1, found := app.state.GetNodeByMachineKey(machineKey1.Public(), types.UserID(1)) require.True(t, found, "original node should still exist for user1") assert.Equal(t, uint(1), node1.UserID().Get(), "original node should still belong to user1") @@ -1775,13 +1775,13 @@ func TestAuthenticationFlows(t *testing.T) { validateCompleteResponse: true, validate: func(t *testing.T, resp *tailcfg.RegisterResponse, app *Headscale) { //nolint:thelper // User1's original node should STILL exist (not transferred) - node1, found1 := app.state.GetNodesByMachineKeyAllUsers(machineKey1.Public())[types.UserID(1)] + node1, found1 := app.state.GetNodeByMachineKey(machineKey1.Public(), types.UserID(1)) require.True(t, found1, "user1's original node should still exist") assert.Equal(t, uint(1), node1.UserID().Get(), "user1's node should still belong to user1") assert.Equal(t, nodeKey1.Public(), node1.NodeKey(), "user1's node should have original node key") // User2 should have a NEW node created - node2, found2 := app.state.GetNodesByMachineKeyAllUsers(machineKey1.Public())[types.UserID(2)] + node2, found2 := app.state.GetNodeByMachineKey(machineKey1.Public(), types.UserID(2)) require.True(t, found2, "user2 should have new node created") assert.Equal(t, uint(2), node2.UserID().Get(), "user2's node should belong to user2") @@ -1845,7 +1845,7 @@ func TestAuthenticationFlows(t *testing.T) { }, // TEST: Logout with expiry exactly at current time // WHAT: Tests logout when expiry is set to exact current time (boundary case) - // INPUT: Existing node sends request with expiry=[time.Now]() (not past, not future) + // INPUT: Existing node sends request with expiry=time.Now() (not past, not future) // EXPECTED: Node is logged out (treated as expired) // WHY: Edge case: current time should be treated as expired { @@ -2225,7 +2225,7 @@ func TestAuthenticationFlows(t *testing.T) { }, // TEST: Interactive workflow with nil hostinfo // WHAT: Tests interactive registration when request has nil hostinfo - // INPUT: Interactive registration request with [tailcfg.Hostinfo]=nil + // INPUT: Interactive registration request with Hostinfo=nil // EXPECTED: Node registers successfully with generated default hostname // WHY: Defensive code handles nil hostinfo in interactive flow { @@ -2761,7 +2761,7 @@ func TestNodeStoreLookup(t *testing.T) { t.Logf("Registered node successfully: %+v", resp) - // Wait for node to be available in [state.NodeStore] + // Wait for node to be available in NodeStore var node types.NodeView require.EventuallyWithT(t, func(c *assert.CollectT) { @@ -2914,7 +2914,7 @@ func TestPreAuthKeyLogoutAndReloginDifferentUser(t *testing.T) { for i := range 2 { node := nodes[i] // User1's original nodes should still be owned by user1 - registeredNode, found := app.state.GetNodesByMachineKeyAllUsers(node.machineKey.Public())[types.UserID(user1.ID)] + registeredNode, found := app.state.GetNodeByMachineKey(node.machineKey.Public(), types.UserID(user1.ID)) require.True(t, found, "User1's original node %s should still exist", node.hostname) require.Equal(t, user1.ID, registeredNode.UserID().Get(), "Node %s should still belong to user1", node.hostname) t.Logf("✓ User1's original node %s (ID=%d) still owned by user1", node.hostname, registeredNode.ID().Uint64()) @@ -2923,7 +2923,7 @@ func TestPreAuthKeyLogoutAndReloginDifferentUser(t *testing.T) { for i := 2; i < 4; i++ { node := nodes[i] // User2's original nodes should still be owned by user2 - registeredNode, found := app.state.GetNodesByMachineKeyAllUsers(node.machineKey.Public())[types.UserID(user2.ID)] + registeredNode, found := app.state.GetNodeByMachineKey(node.machineKey.Public(), types.UserID(user2.ID)) require.True(t, found, "User2's original node %s should still exist", node.hostname) require.Equal(t, user2.ID, registeredNode.UserID().Get(), "Node %s should still belong to user2", node.hostname) t.Logf("✓ User2's original node %s (ID=%d) still owned by user2", node.hostname, registeredNode.ID().Uint64()) @@ -2935,7 +2935,7 @@ func TestPreAuthKeyLogoutAndReloginDifferentUser(t *testing.T) { for i := 2; i < 4; i++ { node := nodes[i] // Should be able to find a node with user1 and this machine key (the new one) - newNode, found := app.state.GetNodesByMachineKeyAllUsers(node.machineKey.Public())[types.UserID(user1.ID)] + newNode, found := app.state.GetNodeByMachineKey(node.machineKey.Public(), types.UserID(user1.ID)) require.True(t, found, "Should have created new node for user1 with machine key from %s", node.hostname) require.Equal(t, user1.ID, newNode.UserID().Get(), "New node should belong to user1") t.Logf("✓ New node created for user1 with machine key from %s (ID=%d)", node.hostname, newNode.ID().Uint64()) @@ -2984,7 +2984,7 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { require.True(t, resp1.MachineAuthorized, "Should be authorized via pre-auth key") // Verify node exists for user1 - user1Node, found := app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user1.ID)] + user1Node, found := app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user1.ID)) require.True(t, found, "Node should exist for user1") require.Equal(t, user1.ID, user1Node.UserID().Get(), "Node should belong to user1") user1NodeID := user1Node.ID() @@ -3000,7 +3000,7 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { require.NoError(t, err) // Verify node is expired - user1Node, found = app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user1.ID)] + user1Node, found = app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user1.ID)) require.True(t, found, "Node should still exist after logout") require.True(t, user1Node.IsExpired(), "Node should be expired after logout") t.Logf("✓ User1 node expired (logged out)") @@ -3041,7 +3041,7 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { t.Run("user1_original_node_still_exists", func(t *testing.T) { // User1's original node should STILL exist (not transferred to user2) - user1NodeAfter, found1 := app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user1.ID)] + user1NodeAfter, found1 := app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user1.ID)) assert.True(t, found1, "User1's original node should still exist (not transferred)") if !found1 { @@ -3056,7 +3056,7 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { t.Run("user2_has_new_node_created", func(t *testing.T) { // User2 should have a NEW node created (not transfer from user1) - user2Node, found2 := app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user2.ID)] + user2Node, found2 := app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user2.ID)) assert.True(t, found2, "User2 should have a new node created") if !found2 { @@ -3072,7 +3072,7 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { }) t.Run("returned_node_is_user2_new_node", func(t *testing.T) { - // The node returned from [state.State.HandleNodeFromAuthPath] should be user2's NEW node + // The node returned from HandleNodeFromAuthPath should be user2's NEW node assert.Equal(t, user2.ID, node.UserID().Get(), "Returned node should belong to user2") assert.NotEqual(t, user1NodeID, node.ID(), "Returned node should be NEW, not transferred from user1") t.Logf("✓ HandleNodeFromAuthPath returned user2's new node (ID: %d)", node.ID()) @@ -3080,8 +3080,8 @@ func TestWebFlowReauthDifferentUser(t *testing.T) { t.Run("both_nodes_share_machine_key", func(t *testing.T) { // Both nodes should have the same machine key (same physical device) - user1NodeFinal, found1 := app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user1.ID)] - user2NodeFinal, found2 := app.state.GetNodesByMachineKeyAllUsers(machineKey.Public())[types.UserID(user2.ID)] + user1NodeFinal, found1 := app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user1.ID)) + user2NodeFinal, found2 := app.state.GetNodeByMachineKey(machineKey.Public(), types.UserID(user2.ID)) require.True(t, found1, "User1 node should exist") require.True(t, found2, "User2 node should exist") @@ -3166,7 +3166,7 @@ func createTestApp(t *testing.T) *Headscale { // 1. Node registers successfully with a single-use pre-auth key // 2. Node is running fine // 3. Node restarts (e.g., after headscale upgrade or tailscale container restart) -// 4. Node sends [tailcfg.RegisterRequest] with the same pre-auth key +// 4. Node sends RegisterRequest with the same pre-auth key // 5. BUG: Headscale rejects the request with "authkey expired" or "authkey already used" // // Expected behavior: @@ -3223,7 +3223,7 @@ func TestGitHubIssue2830_NodeRestartWithUsedPreAuthKey(t *testing.T) { require.NoError(t, err) assert.True(t, usedPak.Used, "pre-auth key should be marked as used after initial registration") - // STEP 2: Simulate node restart - node sends [tailcfg.RegisterRequest] again with same pre-auth key + // STEP 2: Simulate node restart - node sends RegisterRequest again with same pre-auth key // This happens when: // - Tailscale container restarts // - Tailscaled service restarts @@ -3508,7 +3508,7 @@ func TestGitHubIssue2830_ExistingNodeCanReregisterWithUsedPreAuthKey(t *testing. // WITHOUT THE FIX: This would fail with "authkey already used" error // WITH THE FIX: This succeeds because it's the same node re-registering with its own key - // Simulate sending the same [tailcfg.RegisterRequest] again (same MachineKey, same AuthKey) + // Simulate sending the same RegisterRequest again (same MachineKey, same AuthKey) // This is exactly what happens when a container restarts reregisterReq := tailcfg.RegisterRequest{ Auth: &tailcfg.RegisterResponseAuth{ @@ -3787,15 +3787,15 @@ func TestAuthKeyTaggedToUserOwnedViaReauth(t *testing.T) { nodeAfterReauth.IsTagged(), nodeAfterReauth.UserID().Get()) } -// TestDeletedPreAuthKeyNotRecreatedOnNodeUpdate tests that when a [types.PreAuthKey] is deleted, -// subsequent node updates (like those triggered by [tailcfg.MapRequest]s) do not recreate the key. +// TestDeletedPreAuthKeyNotRecreatedOnNodeUpdate tests that when a PreAuthKey is deleted, +// subsequent node updates (like those triggered by MapRequests) do not recreate the key. // // This reproduces the bug where: // 1. Create a tagged preauthkey and register a node // 2. Delete the preauthkey (confirmed gone from pre_auth_keys DB table) -// 3. Node sends [tailcfg.MapRequest] (e.g., after tailscaled restart) +// 3. Node sends MapRequest (e.g., after tailscaled restart) // 4. BUG: The preauthkey reappears because GORM's Updates() upserts the stale AuthKey -// data that still exists in the [state.NodeStore]'s in-memory cache. +// data that still exists in the NodeStore's in-memory cache. // // The fix is to use Omit("AuthKey") on all node Updates() calls to prevent GORM // from touching the AuthKey association. @@ -3864,11 +3864,11 @@ func TestDeletedPreAuthKeyNotRecreatedOnNodeUpdate(t *testing.T) { require.Nil(t, dbNode.AuthKeyID, "node's AuthKeyID should be NULL after PreAuthKey deletion") t.Log("Node's AuthKeyID is NULL in database") - // The [state.NodeStore] may still have stale AuthKey data in memory. - // Now simulate what happens when the node sends a [tailcfg.MapRequest] after a tailscaled restart. - // This triggers [state.State.persistNodeToDB] which calls GORM's Updates(). + // The NodeStore may still have stale AuthKey data in memory. + // Now simulate what happens when the node sends a MapRequest after a tailscaled restart. + // This triggers persistNodeToDB which calls GORM's Updates(). - // Simulate a [tailcfg.MapRequest] by updating the node through the state layer + // Simulate a MapRequest by updating the node through the state layer // This mimics what poll.go does when processing MapRequests mapReq := tailcfg.MapRequest{ NodeKey: nodeKey.Public(), @@ -3879,8 +3879,8 @@ func TestDeletedPreAuthKeyNotRecreatedOnNodeUpdate(t *testing.T) { }, } - // Process the [tailcfg.MapRequest]-like update - // This calls [state.State.UpdateNodeFromMapRequest] which eventually calls [state.State.persistNodeToDB] + // Process the MapRequest-like update + // This calls UpdateNodeFromMapRequest which eventually calls persistNodeToDB _, err = app.state.UpdateNodeFromMapRequest(node.ID(), mapReq) require.NoError(t, err, "UpdateNodeFromMapRequest should succeed") t.Log("Simulated MapRequest update completed") @@ -3943,7 +3943,7 @@ func TestTaggedNodeWithoutUserToDifferentUser(t *testing.T) { alice := app.state.CreateUserForTest("alice") require.NotNil(t, alice, "Alice user should be created") - // Step 4: Re-register the node to alice via [state.State.HandleNodeFromAuthPath] + // Step 4: Re-register the node to alice via HandleNodeFromAuthPath // This is what happens when running: headscale auth register --auth-id --user alice nodeKey2 := key.NewNode() registrationID := types.MustAuthID() @@ -3960,7 +3960,7 @@ func TestTaggedNodeWithoutUserToDifferentUser(t *testing.T) { // This should NOT panic - before the fix, this would panic with: // panic: runtime error: invalid memory address or nil pointer dereference - // at [types.UserView.Name] because the existing node has no User + // at UserView.Name() because the existing node has no User nodeAfterReauth, _, err := app.state.HandleNodeFromAuthPath( registrationID, types.UserID(alice.ID), @@ -3977,8 +3977,8 @@ func TestTaggedNodeWithoutUserToDifferentUser(t *testing.T) { require.False(t, nodeAfterReauth.IsTagged(), "Node should no longer be tagged") require.Empty(t, nodeAfterReauth.Tags().AsSlice(), "Node should have no tags") - // Verify [types.NodeView.Owner] works without panicking - this is what the mapper's - // [generateUserProfiles] calls, and it would panic with a nil pointer + // Verify Owner() works without panicking - this is what the mapper's + // generateUserProfiles calls, and it would panic with a nil pointer // dereference if node.User was not set during the tag→user conversion. owner := nodeAfterReauth.Owner() require.True(t, owner.Valid(), "Owner should be valid after conversion (mapper would panic if nil)") diff --git a/hscontrol/capver/capver.go b/hscontrol/capver/capver.go index a4c535e88..61d674447 100644 --- a/hscontrol/capver/capver.go +++ b/hscontrol/capver/capver.go @@ -3,38 +3,55 @@ package capver //go:generate go run ../../tools/capver/main.go import ( - "maps" "slices" + "sort" "strings" + xmaps "golang.org/x/exp/maps" "tailscale.com/tailcfg" - "tailscale.com/util/cmpver" "tailscale.com/util/set" ) -// minVersionParts is the minimum number of version parts needed for major.minor. -const minVersionParts = 2 +const ( + // minVersionParts is the minimum number of version parts needed for major.minor. + minVersionParts = 2 -// CanOldCodeBeCleanedUp is called at server startup to panic when -// [MinSupportedCapabilityVersion] has crossed a threshold at which a -// backwards-compat emit path can be deleted. Each entry pairs a -// [tailcfg.CapabilityVersion] threshold with the message identifying -// the code to remove; today there are none. + // legacyDERPCapVer is the capability version when LegacyDERP can be cleaned up. + legacyDERPCapVer = 111 +) + +// CanOldCodeBeCleanedUp is intended to be called on startup to see if +// there are old code that can ble cleaned up, entries should contain +// a CapVer where something can be cleaned up and a panic if it can. +// This is only intended to catch things in tests. // -// All capability-version-gated cleanups should be registered here. +// All uses of Capability version checks should be listed here. func CanOldCodeBeCleanedUp() { + if MinSupportedCapabilityVersion >= legacyDERPCapVer { + panic("LegacyDERP can be cleaned up in tail.go") + } } func tailscaleVersSorted() []string { - return slices.Sorted(maps.Keys(tailscaleToCapVer)) + vers := xmaps.Keys(tailscaleToCapVer) + sort.Strings(vers) + + return vers } -// TailscaleVersion returns the Tailscale version for the given [tailcfg.CapabilityVersion]. +func capVersSorted() []tailcfg.CapabilityVersion { + capVers := xmaps.Keys(capVerToTailscaleVer) + slices.Sort(capVers) + + return capVers +} + +// TailscaleVersion returns the Tailscale version for the given CapabilityVersion. func TailscaleVersion(ver tailcfg.CapabilityVersion) string { return capVerToTailscaleVer[ver] } -// CapabilityVersion returns the [tailcfg.CapabilityVersion] for the given Tailscale version. +// CapabilityVersion returns the CapabilityVersion for the given Tailscale version. // It accepts both full versions (v1.90.1) and minor versions (v1.90). func CapabilityVersion(ver string) tailcfg.CapabilityVersion { if !strings.HasPrefix(ver, "v") { @@ -56,6 +73,21 @@ func CapabilityVersion(ver string) tailcfg.CapabilityVersion { return 0 } +// TailscaleLatest returns the n latest Tailscale versions. +func TailscaleLatest(n int) []string { + if n <= 0 { + return nil + } + + tsSorted := tailscaleVersSorted() + + if n > len(tsSorted) { + return tsSorted + } + + return tsSorted[len(tsSorted)-n:] +} + // TailscaleLatestMajorMinor returns the n latest Tailscale versions (e.g. 1.80). func TailscaleLatestMajorMinor(n int, stripV bool) []string { if n <= 0 { @@ -74,9 +106,7 @@ func TailscaleLatestMajorMinor(n int, stripV bool) []string { } majorSl := majors.Slice() - // cmpver orders versions numerically, so v1.100 sorts after v1.98 rather than - // lexically before it. - slices.SortFunc(majorSl, cmpver.Compare) + sort.Strings(majorSl) if n > len(majorSl) { return majorSl @@ -84,3 +114,18 @@ func TailscaleLatestMajorMinor(n int, stripV bool) []string { return majorSl[len(majorSl)-n:] } + +// CapVerLatest returns the n latest CapabilityVersions. +func CapVerLatest(n int) []tailcfg.CapabilityVersion { + if n <= 0 { + return nil + } + + s := capVersSorted() + + if n > len(s) { + return s + } + + return s[len(s)-n:] +} diff --git a/hscontrol/capver/capver_generated.go b/hscontrol/capver/capver_generated.go index 1091f42b6..72318ae58 100644 --- a/hscontrol/capver/capver_generated.go +++ b/hscontrol/capver/capver_generated.go @@ -42,7 +42,6 @@ var tailscaleToCapVer = map[string]tailcfg.CapabilityVersion{ "v1.92": 131, "v1.94": 131, "v1.96": 133, - "v1.98": 138, } var capVerToTailscaleVer = map[tailcfg.CapabilityVersion]string{ @@ -78,7 +77,6 @@ var capVerToTailscaleVer = map[tailcfg.CapabilityVersion]string{ 130: "v1.90", 131: "v1.92", 133: "v1.96", - 138: "v1.98", } // SupportedMajorMinorVersions is the number of major.minor Tailscale versions supported. @@ -86,4 +84,4 @@ const SupportedMajorMinorVersions = 10 // MinSupportedCapabilityVersion represents the minimum capability version // supported by this Headscale instance (latest 10 minor versions) -const MinSupportedCapabilityVersion tailcfg.CapabilityVersion = 113 +const MinSupportedCapabilityVersion tailcfg.CapabilityVersion = 109 diff --git a/hscontrol/capver/capver_test_data.go b/hscontrol/capver/capver_test_data.go index 7309459ac..83bccc08d 100644 --- a/hscontrol/capver/capver_test_data.go +++ b/hscontrol/capver/capver_test_data.go @@ -9,9 +9,10 @@ var tailscaleLatestMajorMinorTests = []struct { stripV bool expected []string }{ - {3, false, []string{"v1.94", "v1.96", "v1.98"}}, - {2, true, []string{"1.96", "1.98"}}, + {3, false, []string{"v1.92", "v1.94", "v1.96"}}, + {2, true, []string{"1.94", "1.96"}}, {10, true, []string{ + "1.78", "1.80", "1.82", "1.84", @@ -21,7 +22,6 @@ var tailscaleLatestMajorMinorTests = []struct { "1.92", "1.94", "1.96", - "1.98", }}, {0, false, nil}, } @@ -30,7 +30,7 @@ var capVerMinimumTailscaleVersionTests = []struct { input tailcfg.CapabilityVersion expected string }{ - {113, "v1.80"}, + {109, "v1.78"}, {32, "v1.24"}, {41, "v1.30"}, {46, "v1.32"}, diff --git a/hscontrol/db/api_key.go b/hscontrol/db/api_key.go index b56f1c188..9eeaf7e61 100644 --- a/hscontrol/db/api_key.go +++ b/hscontrol/db/api_key.go @@ -7,9 +7,9 @@ import ( "time" "github.com/juanfont/headscale/hscontrol/types" + "github.com/juanfont/headscale/hscontrol/util" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" - "tailscale.com/util/rands" ) const ( @@ -23,20 +23,44 @@ const ( ) var ( - ErrAPIKeyFailedToParse = errors.New("failed to parse ApiKey") - ErrAPIKeyGenerationFailed = errors.New("failed to generate API key") - ErrAPIKeyExpired = errors.New("API key expired") + ErrAPIKeyFailedToParse = errors.New("failed to parse ApiKey") + ErrAPIKeyGenerationFailed = errors.New("failed to generate API key") + ErrAPIKeyInvalidGeneration = errors.New("generated API key failed validation") ) -// CreateAPIKey creates a new [types.APIKey] in a user, and returns it. +// CreateAPIKey creates a new ApiKey in a user, and returns it. func (hsdb *HSDatabase) CreateAPIKey( expiration *time.Time, ) (string, *types.APIKey, error) { // Generate public prefix (12 chars) - prefix := rands.HexString(apiKeyPrefixLength) + prefix, err := util.GenerateRandomStringURLSafe(apiKeyPrefixLength) + if err != nil { + return "", nil, err + } + + // Validate prefix + if len(prefix) != apiKeyPrefixLength { + return "", nil, fmt.Errorf("%w: generated prefix has invalid length: expected %d, got %d", ErrAPIKeyInvalidGeneration, apiKeyPrefixLength, len(prefix)) + } + + if !isValidBase64URLSafe(prefix) { + return "", nil, fmt.Errorf("%w: generated prefix contains invalid characters", ErrAPIKeyInvalidGeneration) + } // Generate secret (64 chars) - secret := rands.HexString(apiKeyHashLength) + secret, err := util.GenerateRandomStringURLSafe(apiKeyHashLength) + if err != nil { + return "", nil, err + } + + // Validate secret + if len(secret) != apiKeyHashLength { + return "", nil, fmt.Errorf("%w: generated secret has invalid length: expected %d, got %d", ErrAPIKeyInvalidGeneration, apiKeyHashLength, len(secret)) + } + + if !isValidBase64URLSafe(secret) { + return "", nil, fmt.Errorf("%w: generated secret contains invalid characters", ErrAPIKeyInvalidGeneration) + } // Full key string (shown ONCE to user) keyStr := apiKeyPrefix + prefix + "-" + secret @@ -60,7 +84,7 @@ func (hsdb *HSDatabase) CreateAPIKey( return keyStr, &key, nil } -// ListAPIKeys returns the list of [types.APIKey] values for a user. +// ListAPIKeys returns the list of ApiKeys for a user. func (hsdb *HSDatabase) ListAPIKeys() ([]types.APIKey, error) { keys := []types.APIKey{} @@ -72,7 +96,7 @@ func (hsdb *HSDatabase) ListAPIKeys() ([]types.APIKey, error) { return keys, nil } -// GetAPIKey returns a [types.APIKey] for a given key. +// GetAPIKey returns a ApiKey for a given key. func (hsdb *HSDatabase) GetAPIKey(prefix string) (*types.APIKey, error) { key := types.APIKey{} if result := hsdb.DB.First(&key, "prefix = ?", prefix); result.Error != nil { @@ -82,20 +106,17 @@ func (hsdb *HSDatabase) GetAPIKey(prefix string) (*types.APIKey, error) { return &key, nil } -// GetAPIKeyByID returns a [types.APIKey] for a given id. +// GetAPIKeyByID returns a ApiKey for a given id. func (hsdb *HSDatabase) GetAPIKeyByID(id uint64) (*types.APIKey, error) { key := types.APIKey{} - // Query on an explicit primary-key clause: a struct condition would drop a - // zero-valued ID, making the lookup unconditional and returning the first - // row instead of not-found. - if result := hsdb.DB.First(&key, "id = ?", id); result.Error != nil { + if result := hsdb.DB.Find(&types.APIKey{ID: id}).First(&key); result.Error != nil { return nil, result.Error } return &key, nil } -// DestroyAPIKey destroys a [types.APIKey]. Returns error if the [types.APIKey] +// DestroyAPIKey destroys a ApiKey. Returns error if the ApiKey // does not exist. func (hsdb *HSDatabase) DestroyAPIKey(key types.APIKey) error { if result := hsdb.DB.Unscoped().Delete(key); result.Error != nil { @@ -105,7 +126,7 @@ func (hsdb *HSDatabase) DestroyAPIKey(key types.APIKey) error { return nil } -// ExpireAPIKey marks a [types.APIKey] as expired. +// ExpireAPIKey marks a ApiKey as expired. func (hsdb *HSDatabase) ExpireAPIKey(key *types.APIKey) error { err := hsdb.DB.Model(&key).Update("Expiration", time.Now()).Error if err != nil { @@ -128,31 +149,6 @@ func (hsdb *HSDatabase) ValidateAPIKey(keyStr string) (bool, error) { return true, nil } -// AuthenticateAPIKey validates keyStr and returns the matching, unexpired -// [types.APIKey] (with its owning UserID populated). Unlike ValidateAPIKey it -// returns the key itself, so the v2 API can act as the key's owning user. A -// non-nil error means the key is missing, malformed, or expired. -func (hsdb *HSDatabase) AuthenticateAPIKey(keyStr string) (*types.APIKey, error) { - key, err := validateAPIKey(hsdb.DB, keyStr) - if err != nil { - return nil, err - } - - if key.Expiration != nil && key.Expiration.Before(time.Now()) { - return nil, ErrAPIKeyExpired - } - - return key, nil -} - -// SetAPIKeyUser sets the owning user of an API key. Used when an admin mints a -// key on behalf of a user (headscale apikeys create --user). -func (hsdb *HSDatabase) SetAPIKeyUser(keyID uint64, userID types.UserID) error { - return hsdb.DB.Model(&types.APIKey{}). - Where("id = ?", keyID). - Update("user_id", uint(userID)).Error -} - // ParseAPIKeyPrefix extracts the database prefix from a display prefix. // Handles formats: "hskey-api-{12chars}-***", "hskey-api-{12chars}", or just "{12chars}". // Returns the 12-character prefix suitable for database lookup. @@ -206,20 +202,61 @@ func validateAPIKey(db *gorm.DB, keyStr string) (*types.APIKey, error) { } // New format: parse and verify - prefix, secret, err := parsePrefixedKey( - prefixAndSecret, - apiKeyPrefixLength, - apiKeyHashLength, - ErrAPIKeyFailedToParse, - ) - if err != nil { - return nil, err + const expectedMinLength = apiKeyPrefixLength + 1 + apiKeyHashLength + if len(prefixAndSecret) < expectedMinLength { + return nil, fmt.Errorf( + "%w: key too short, expected at least %d chars after prefix, got %d", + ErrAPIKeyFailedToParse, + expectedMinLength, + len(prefixAndSecret), + ) + } + + // Use fixed-length parsing + prefix := prefixAndSecret[:apiKeyPrefixLength] + + // Validate separator at expected position + if prefixAndSecret[apiKeyPrefixLength] != '-' { + return nil, fmt.Errorf( + "%w: expected separator '-' at position %d, got '%c'", + ErrAPIKeyFailedToParse, + apiKeyPrefixLength, + prefixAndSecret[apiKeyPrefixLength], + ) + } + + secret := prefixAndSecret[apiKeyPrefixLength+1:] + + // Validate secret length + if len(secret) != apiKeyHashLength { + return nil, fmt.Errorf( + "%w: secret length mismatch, expected %d chars, got %d", + ErrAPIKeyFailedToParse, + apiKeyHashLength, + len(secret), + ) + } + + // Validate prefix contains only base64 URL-safe characters + if !isValidBase64URLSafe(prefix) { + return nil, fmt.Errorf( + "%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", + ErrAPIKeyFailedToParse, + ) + } + + // Validate secret contains only base64 URL-safe characters + if !isValidBase64URLSafe(secret) { + return nil, fmt.Errorf( + "%w: secret contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", + ErrAPIKeyFailedToParse, + ) } // Look up by prefix (indexed) var key types.APIKey - err = db.First(&key, "prefix = ?", prefix).Error + err := db.First(&key, "prefix = ?", prefix).Error if err != nil { return nil, fmt.Errorf("API key not found: %w", err) } diff --git a/hscontrol/db/api_key_byid_test.go b/hscontrol/db/api_key_byid_test.go deleted file mode 100644 index 05822c0b2..000000000 --- a/hscontrol/db/api_key_byid_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package db - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestGetAPIKeyByIDZeroReturnsError ensures GetAPIKeyByID(0) reports not-found -// rather than returning the lowest-ID key. GORM drops a zero-valued primary key -// from a struct condition, which would otherwise make the lookup unconditional. -func TestGetAPIKeyByIDZeroReturnsError(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - _, key1, err := db.CreateAPIKey(nil) - require.NoError(t, err) - require.NotNil(t, key1) - - _, key2, err := db.CreateAPIKey(nil) - require.NoError(t, err) - require.NotNil(t, key2) - - key, err := db.GetAPIKeyByID(0) - require.Error(t, err, "GetAPIKeyByID(0) should be not-found, got key=%+v", key) - assert.Nil(t, key) -} diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index c9b94ef6c..d95b7496e 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -38,9 +38,9 @@ var errDatabaseNotSupported = errors.New("database type not supported") var errForeignKeyConstraintsViolated = errors.New("foreign key constraints violated") const ( - maxIdleConns = 100 - maxOpenConns = 100 - contextTimeout = 10 * time.Second + maxIdleConns = 100 + maxOpenConns = 100 + contextTimeoutSecs = 10 ) type HSDatabase struct { @@ -220,7 +220,7 @@ AND auth_key_id NOT IN ( { ID: "202505141324", Migrate: func(tx *gorm.DB) error { - users, err := ListUsers(tx, nil) + users, err := ListUsers(tx) if err != nil { return fmt.Errorf("listing users: %w", err) } @@ -617,7 +617,7 @@ AND auth_key_id NOT IN ( } // 2. Load users and nodes to create PolicyManager - users, err := ListUsers(tx, nil) + users, err := ListUsers(tx) if err != nil { return fmt.Errorf("loading users for RequestTags migration: %w", err) } @@ -706,20 +706,13 @@ AND auth_key_id NOT IN ( // but this prevents deleting users whose nodes have been // tagged, and the ON DELETE CASCADE FK would destroy the // tagged nodes if the user were deleted. - // - // A nil tags slice marshals to the JSON literal 'null', so - // untagged nodes can carry tags='null'. That spelling must be - // excluded alongside '[]' and '' or untagged nodes lose their - // user. Nodes already detached by the earlier version of this - // migration are repaired by the recovery migration below. // Fixes: https://github.com/juanfont/headscale/issues/3077 - // Fixes: https://github.com/juanfont/headscale/issues/3323 ID: "202602201200-clear-tagged-node-user-id", Migrate: func(tx *gorm.DB) error { err := tx.Exec(` UPDATE nodes SET user_id = NULL -WHERE tags IS NOT NULL AND tags != '[]' AND tags != '' AND tags != 'null'; +WHERE tags IS NOT NULL AND tags != '[]' AND tags != ''; `).Error if err != nil { return fmt.Errorf("clearing user_id on tagged nodes: %w", err) @@ -729,177 +722,6 @@ WHERE tags IS NOT NULL AND tags != '[]' AND tags != '' AND tags != 'null'; }, Rollback: func(db *gorm.DB) error { return nil }, }, - { - // Clear zero-time node expiry values to NULL. - // Versions before 0.28 persisted a pointer to a zero - // time.Time as '0001-01-01 00:00:00+00:00' rather than - // NULL, which 0.29 reports as an expired node. This - // normalises the existing rows so the column once - // again means "no expiry" when unset. - ID: "202605221435-clear-zero-time-node-expiry", - Migrate: func(tx *gorm.DB) error { - err := tx.Exec(` -UPDATE nodes -SET expiry = NULL -WHERE expiry IS NOT NULL AND expiry < '1900-01-01'; - `).Error - if err != nil { - return fmt.Errorf("clearing zero-time node expiry: %w", err) - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, - { - // Recover user_id on untagged nodes detached by the earlier - // version of 202602201200-clear-tagged-node-user-id, which - // treated tags='null' as tagged and cleared the user. This - // repairs databases that already upgraded to 0.29.0; fresh - // upgrades are protected by the fixed migration above and find - // nothing to repair. Recovery is best-effort: the owner is - // re-derived from the node's pre-auth key, so nodes registered - // via CLI/OIDC (no pre-auth key) cannot be recovered and must - // be reassigned manually. - // Fixes: https://github.com/juanfont/headscale/issues/3323 - ID: "202606181200-recover-null-tags-node-user-id", - Migrate: func(tx *gorm.DB) error { - err := tx.Exec(` -UPDATE nodes -SET user_id = ( - SELECT pak.user_id FROM pre_auth_keys pak WHERE pak.id = nodes.auth_key_id -) -WHERE user_id IS NULL - AND auth_key_id IS NOT NULL - AND (tags IS NULL OR tags = '' OR tags = '[]' OR tags = 'null'); - `).Error - if err != nil { - return fmt.Errorf("recovering user_id on untagged nodes: %w", err) - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, - { - // Add an optional owning user to API keys so the v2 API can - // create user-owned (untagged) auth keys, mirroring Tailscale's - // "key owned by the creating identity". - ID: "202606191500-api-key-user-id", - Migrate: func(tx *gorm.DB) error { - if !tx.Migrator().HasColumn(&types.APIKey{}, "user_id") { - err := tx.Migrator().AddColumn(&types.APIKey{}, "user_id") - if err != nil { - return fmt.Errorf("adding user_id to api_keys: %w", err) - } - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, - { - // Add a free-text description to pre-auth keys, set via the - // v2 keys API. - ID: "202606191501-pre-auth-key-description", - Migrate: func(tx *gorm.DB) error { - if !tx.Migrator().HasColumn(&types.PreAuthKey{}, "description") { - err := tx.Migrator().AddColumn(&types.PreAuthKey{}, "description") - if err != nil { - return fmt.Errorf("adding description to pre_auth_keys: %w", err) - } - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, - { - // Add a revoked timestamp to pre-auth keys. The v2 API's DELETE - // soft-revokes a key (set revoked = now) rather than destroying - // it; the row is reaped later by the background collector. - ID: "202606201200-pre-auth-key-revoked", - Migrate: func(tx *gorm.DB) error { - if !tx.Migrator().HasColumn(&types.PreAuthKey{}, "revoked") { - err := tx.Migrator().AddColumn(&types.PreAuthKey{}, "revoked") - if err != nil { - return fmt.Errorf("adding revoked to pre_auth_keys: %w", err) - } - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, - { - // Add the OAuth client + access token tables backing the v2 API's - // OAuth client-credentials flow. They mirror the api_keys / - // pre_auth_keys security model: a public id/prefix plus an Argon2id - // hash of the secret. - // - // SQLite uses explicit DDL that matches schema.sql byte-for-byte - // (the squibble digest is the SQLite source of truth). Postgres, - // which has no digest and rejects SQLite-isms like AUTOINCREMENT, - // uses dialect-aware AutoMigrate, mirroring InitSchema's fresh-DB - // table creation so an existing Postgres deployment can upgrade. - ID: "202606211200-oauth-clients-and-tokens", - Migrate: func(tx *gorm.DB) error { - if tx.Migrator().HasTable(&types.OAuthClient{}) && - tx.Migrator().HasTable(&types.OAuthAccessToken{}) { - return nil - } - - if tx.Name() != "sqlite" { - return tx.AutoMigrate(&types.OAuthClient{}, &types.OAuthAccessToken{}) - } - - if !tx.Migrator().HasTable(&types.OAuthClient{}) { - err := tx.Exec(`CREATE TABLE oauth_clients( - id integer PRIMARY KEY AUTOINCREMENT, - client_id text, - secret_hash blob, - scopes text, - tags text, - description text, - user_id integer, - created_at datetime, - revoked datetime -)`).Error - if err != nil { - return fmt.Errorf("creating oauth_clients table: %w", err) - } - - err = tx.Exec(`CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id)`).Error - if err != nil { - return fmt.Errorf("creating oauth_clients index: %w", err) - } - } - - if !tx.Migrator().HasTable(&types.OAuthAccessToken{}) { - err := tx.Exec(`CREATE TABLE oauth_access_tokens( - id integer PRIMARY KEY AUTOINCREMENT, - prefix text, - hash blob, - client_id text, - scopes text, - tags text, - expiration datetime, - created_at datetime -)`).Error - if err != nil { - return fmt.Errorf("creating oauth_access_tokens table: %w", err) - } - - err = tx.Exec(`CREATE UNIQUE INDEX idx_oauth_access_tokens_prefix ON oauth_access_tokens(prefix)`).Error - if err != nil { - return fmt.Errorf("creating oauth_access_tokens index: %w", err) - } - } - - return nil - }, - Rollback: func(db *gorm.DB) error { return nil }, - }, }, ) @@ -911,8 +733,6 @@ WHERE user_id IS NULL &types.APIKey{}, &types.Node{}, &types.Policy{}, - &types.OAuthClient{}, - &types.OAuthAccessToken{}, ) if err != nil { return err @@ -928,8 +748,6 @@ WHERE user_id IS NULL `DROP INDEX IF EXISTS "idx_name_provider_identifier"`, `DROP INDEX IF EXISTS "idx_name_no_provider_identifier"`, `DROP INDEX IF EXISTS "idx_pre_auth_keys_prefix"`, - `DROP INDEX IF EXISTS "idx_oauth_clients_client_id"`, - `DROP INDEX IF EXISTS "idx_oauth_access_tokens_prefix"`, } for _, dropSQL := range dropIndexes { @@ -948,8 +766,6 @@ WHERE user_id IS NULL `CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier)`, `CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL`, `CREATE UNIQUE INDEX idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''`, - `CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id)`, - `CREATE UNIQUE INDEX idx_oauth_access_tokens_prefix ON oauth_access_tokens(prefix)`, } for _, indexSQL := range indexes { @@ -998,7 +814,7 @@ WHERE user_id IS NULL defer sqlConn.SetMaxIdleConns(1) defer sqlConn.SetMaxOpenConns(1) - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeoutSecs*time.Second) defer cancel() opts := squibble.DigestOptions{ @@ -1132,20 +948,65 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) { func runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormigrate.Gormigrate) error { if cfg.Type == types.DatabaseSqlite { - // SQLite: Run the early migrations that GORM cannot handle safely with - // foreign keys enabled (route and pre-auth-key automigrations) with FK - // disabled, then run everything else with FK enabled. - // - // NO NEW MIGRATIONS SHOULD RUN WITH FK DISABLED. As of 2025-07-02, all - // new migrations must run with foreign keys enabled via the - // migrations.Migrate() call below. - if err := dbConn.Exec("PRAGMA foreign_keys = OFF").Error; err != nil { //nolint:noinlineerr - return fmt.Errorf("disabling foreign keys: %w", err) + // SQLite: Run migrations step-by-step, only disabling foreign keys when necessary + + // List of migration IDs that require foreign keys to be disabled + // These are migrations that perform complex schema changes that GORM cannot handle safely with FK enabled + // NO NEW MIGRATIONS SHOULD BE ADDED HERE. ALL NEW MIGRATIONS MUST RUN WITH FOREIGN KEYS ENABLED. + migrationsRequiringFKDisabled := map[string]bool{ + "202501221827": true, // Route table automigration with FK constraint issues + "202501311657": true, // PreAuthKey table automigration with FK constraint issues + // Add other migration IDs here as they are identified to need FK disabled } - // Run up to and including the last migration that requires FK disabled. - if err := migrations.MigrateTo("202501311657"); err != nil { //nolint:noinlineerr - return fmt.Errorf("running migration 202501311657: %w", err) + // Get the current foreign key status + var fkOriginallyEnabled int + if err := dbConn.Raw("PRAGMA foreign_keys").Scan(&fkOriginallyEnabled).Error; err != nil { //nolint:noinlineerr + return fmt.Errorf("checking foreign key status: %w", err) + } + + // Get all migration IDs in order from the actual migration definitions + // Only IDs that are in the migrationsRequiringFKDisabled map will be processed with FK disabled + // any other new migrations are ran after. + migrationIDs := []string{ + // v0.25.0 + "202501221827", + "202501311657", + "202502070949", + + // v0.26.0 + "202502131714", + "202502171819", + "202505091439", + "202505141324", + + // As of 2025-07-02, no new IDs should be added here. + // They will be ran by the migrations.Migrate() call below. + } + + for _, migrationID := range migrationIDs { + log.Trace().Caller().Str("migration_id", migrationID).Msg("running migration") + needsFKDisabled := migrationsRequiringFKDisabled[migrationID] + + if needsFKDisabled { + // Disable foreign keys for this migration + err := dbConn.Exec("PRAGMA foreign_keys = OFF").Error + if err != nil { + return fmt.Errorf("disabling foreign keys for migration %s: %w", migrationID, err) + } + } else { + // Ensure foreign keys are enabled for this migration + err := dbConn.Exec("PRAGMA foreign_keys = ON").Error + if err != nil { + return fmt.Errorf("enabling foreign keys for migration %s: %w", migrationID, err) + } + } + + // Run up to this specific migration (will only run the next pending migration) + err := migrations.MigrateTo(migrationID) + if err != nil { + return fmt.Errorf("running migration %s: %w", migrationID, err) + } } if err := dbConn.Exec("PRAGMA foreign_keys = ON").Error; err != nil { //nolint:noinlineerr diff --git a/hscontrol/db/db_test.go b/hscontrol/db/db_test.go index 88d282c46..ee88d4aef 100644 --- a/hscontrol/db/db_test.go +++ b/hscontrol/db/db_test.go @@ -36,7 +36,7 @@ func TestSQLiteMigrationAndDataValidation(t *testing.T) { // Verify users data preservation users, err := Read(hsdb.DB, func(rx *gorm.DB) ([]types.User, error) { - return ListUsers(rx, nil) + return ListUsers(rx) }) require.NoError(t, err) assert.Len(t, users, 1, "should preserve all 1 user from original schema") @@ -122,7 +122,7 @@ func TestSQLiteMigrationAndDataValidation(t *testing.T) { // Expected: tags = ["tag:server"] (no duplicates) node4 := findNode("node4") require.NotNil(t, node4, "node4 should exist") - assert.Equal(t, []string{"tag:server"}, node4.Tags.List(), "node4 should have tag:server without duplicates") //nolint:goconst // descriptive test assertions read better with the literal inline + assert.Equal(t, []string{"tag:server"}, node4.Tags, "node4 should have tag:server without duplicates") // Node 5: user2 has no RequestTags // Expected: tags = [] (unchanged) @@ -144,157 +144,6 @@ func TestSQLiteMigrationAndDataValidation(t *testing.T) { assert.NotContains(t, node7.Tags, "tag:forbidden", "node7 should NOT have tag:forbidden (unauthorized)") }, }, - // Test for the zero-time node expiry migration - // (202605221435-clear-zero-time-node-expiry). Pre-0.28 versions - // stored a zero time.Time as '0001-01-01 00:00:00+00:00' rather - // than NULL, which caused 0.29 to report those nodes as expired. - // Fixes: https://github.com/juanfont/headscale/issues/3284 - { - dbPath: "testdata/sqlite/zero_time_expiry_migration_test.sql", - wantFunc: func(t *testing.T, hsdb *HSDatabase) { - t.Helper() - - nodes, err := Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) { - return ListNodes(rx) - }) - require.NoError(t, err) - require.Len(t, nodes, 5, "should have all 5 nodes") - - byHostname := make(map[string]*types.Node, len(nodes)) - for _, n := range nodes { - byHostname[n.Hostname] = n - } - - // Node 1 had a zero-time expiry; should be cleared. - node1 := byHostname["node1"] - require.NotNil(t, node1, "node1 should exist") - assert.Nil(t, node1.Expiry, "node1 zero-time expiry should be cleared to NULL") - assert.False(t, node1.IsExpired(), "node1 should not be reported as expired") - - // Node 2 already had NULL expiry; should still be NULL. - node2 := byHostname["node2"] - require.NotNil(t, node2, "node2 should exist") - assert.Nil(t, node2.Expiry, "node2 NULL expiry should be preserved") - assert.False(t, node2.IsExpired(), "node2 should not be reported as expired") - - // Node 3 had a real future expiry; should be preserved. - node3 := byHostname["node3"] - require.NotNil(t, node3, "node3 should exist") - require.NotNil(t, node3.Expiry, "node3 future expiry should be preserved") - assert.Equal(t, 2099, node3.Expiry.UTC().Year(), "node3 expiry year should be 2099") - assert.False(t, node3.IsExpired(), "node3 with future expiry should not be expired") - - // Node 4 had a real past expiry; should be preserved. - node4 := byHostname["node4"] - require.NotNil(t, node4, "node4 should exist") - require.NotNil(t, node4.Expiry, "node4 past expiry should be preserved") - assert.Equal(t, 2020, node4.Expiry.UTC().Year(), "node4 expiry year should be 2020") - assert.True(t, node4.IsExpired(), "node4 with past expiry should still be expired") - - // Node 5 also had a zero-time expiry; should be cleared. - node5 := byHostname["node5"] - require.NotNil(t, node5, "node5 should exist") - assert.Nil(t, node5.Expiry, "node5 zero-time expiry should be cleared to NULL") - assert.False(t, node5.IsExpired(), "node5 should not be reported as expired") - }, - }, - // Test for the clear-tagged-node-user-id migration - // (202602201200-clear-tagged-node-user-id). A nil tags slice - // marshals to the JSON literal 'null', so untagged nodes can carry - // tags='null' in the database. The migration must only clear - // user_id on genuinely tagged nodes, not on these untagged ones. - // Fixes: https://github.com/juanfont/headscale/issues/3323 - { - dbPath: "testdata/sqlite/null_tags_user_id_migration_test.sql", - wantFunc: func(t *testing.T, hsdb *HSDatabase) { - t.Helper() - - nodes, err := Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) { - return ListNodes(rx) - }) - require.NoError(t, err) - require.Len(t, nodes, 4, "should have all 4 nodes") - - byHostname := make(map[string]*types.Node, len(nodes)) - for _, n := range nodes { - byHostname[n.Hostname] = n - } - - // Node 1 had tags='null' (untagged) and belonged to user2. - // The migration must NOT clear its user_id. - node1 := byHostname["node1"] - require.NotNil(t, node1, "node1 should exist") - assert.False(t, node1.IsTagged(), "node1 with tags='null' should be untagged") - require.NotNil(t, node1.UserID, "node1 should keep its user assigned") - assert.Equal(t, uint(2), *node1.UserID, "node1 should still belong to user2") - - // Node 2 is genuinely tagged; user_id must be cleared. - node2 := byHostname["node2"] - require.NotNil(t, node2, "node2 should exist") - assert.True(t, node2.IsTagged(), "node2 should be tagged") - assert.Nil(t, node2.UserID, "node2 (tagged) should have user_id cleared") - - // Node 3 had tags='[]' (untagged); user_id preserved. - node3 := byHostname["node3"] - require.NotNil(t, node3, "node3 should exist") - assert.False(t, node3.IsTagged(), "node3 with tags='[]' should be untagged") - require.NotNil(t, node3.UserID, "node3 should keep its user assigned") - assert.Equal(t, uint(1), *node3.UserID, "node3 should still belong to user1") - - // Node 4 had tags='' (untagged); user_id preserved. - node4 := byHostname["node4"] - require.NotNil(t, node4, "node4 should exist") - assert.False(t, node4.IsTagged(), "node4 with tags='' should be untagged") - require.NotNil(t, node4.UserID, "node4 should keep its user assigned") - assert.Equal(t, uint(1), *node4.UserID, "node4 should still belong to user1") - }, - }, - // Test for the null-tags user_id recovery migration. Databases that - // already upgraded to 0.29.0 had user_id wrongly cleared on untagged - // nodes with tags='null'. The recovery migration re-derives user_id - // from the node's pre-auth key where one exists. - // Fixes: https://github.com/juanfont/headscale/issues/3323 - { - dbPath: "testdata/sqlite/recover_null_tags_user_id_migration_test.sql", - wantFunc: func(t *testing.T, hsdb *HSDatabase) { - t.Helper() - - nodes, err := Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) { - return ListNodes(rx) - }) - require.NoError(t, err) - require.Len(t, nodes, 4, "should have all 4 nodes") - - byHostname := make(map[string]*types.Node, len(nodes)) - for _, n := range nodes { - byHostname[n.Hostname] = n - } - - // Node 1: authkey-registered, orphaned by the bug. The recovery - // migration restores user_id from its pre-auth key (user2). - node1 := byHostname["node1"] - require.NotNil(t, node1, "node1 should exist") - require.NotNil(t, node1.UserID, "node1 user_id should be recovered") - assert.Equal(t, uint(2), *node1.UserID, "node1 should be recovered to user2") - - // Node 2: genuinely tagged, correctly cleared. Must stay cleared. - node2 := byHostname["node2"] - require.NotNil(t, node2, "node2 should exist") - assert.True(t, node2.IsTagged(), "node2 should be tagged") - assert.Nil(t, node2.UserID, "node2 (tagged) must remain cleared") - - // Node 3: CLI-registered, no pre-auth key. Unrecoverable. - node3 := byHostname["node3"] - require.NotNil(t, node3, "node3 should exist") - assert.Nil(t, node3.UserID, "node3 has no pre-auth key to recover from") - - // Node 4: never orphaned; user_id must be untouched. - node4 := byHostname["node4"] - require.NotNil(t, node4, "node4 should exist") - require.NotNil(t, node4.UserID, "node4 user_id should be untouched") - assert.Equal(t, uint(1), *node4.UserID, "node4 should still belong to user1") - }, - }, } for _, tt := range tests { diff --git a/hscontrol/db/ephemeral_garbage_collector_test.go b/hscontrol/db/ephemeral_garbage_collector_test.go index a04c2103f..10bb235a7 100644 --- a/hscontrol/db/ephemeral_garbage_collector_test.go +++ b/hscontrol/db/ephemeral_garbage_collector_test.go @@ -2,7 +2,6 @@ package db import ( "runtime" - "slices" "sync" "sync/atomic" "testing" @@ -10,7 +9,6 @@ import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const ( @@ -19,8 +17,8 @@ const ( fifty = 50 * time.Millisecond ) -// TestEphemeralGarbageCollectorGoRoutineLeak is a test for a goroutine leak in [EphemeralGarbageCollector]. -// It creates a new [EphemeralGarbageCollector], schedules several nodes for deletion with a short expiry, +// TestEphemeralGarbageCollectorGoRoutineLeak is a test for a goroutine leak in EphemeralGarbageCollector(). +// It creates a new EphemeralGarbageCollector, schedules several nodes for deletion with a short expiry, // and verifies that the nodes are deleted when the expiry time passes, and then // for any leaked goroutines after the garbage collector is closed. func TestEphemeralGarbageCollectorGoRoutineLeak(t *testing.T) { @@ -91,84 +89,8 @@ func TestEphemeralGarbageCollectorGoRoutineLeak(t *testing.T) { t.Logf("Final number of goroutines: %d", runtime.NumGoroutine()) } -// TestEphemeralGarbageCollectorCancelReapsGoroutine verifies that Cancel (and -// reschedule) reaps the per-node watcher goroutine while the collector is still -// running, rather than leaking it until Close. The production churn path is an -// ephemeral node disconnecting (Schedule) then reconnecting (Cancel) before the -// long expiry timer fires; a stopped timer never fires, so a watcher parked -// only on <-timer.C would otherwise leak on every cycle. -func TestEphemeralGarbageCollectorCancelReapsGoroutine(t *testing.T) { - gc := NewEphemeralGarbageCollector(func(types.NodeID) {}) - - go gc.Start() - defer gc.Close() - - baseline := runtime.NumGoroutine() - - const ( - iterations = 1000 - nodeID = types.NodeID(42) - ) - - for range iterations { - gc.Schedule(nodeID, time.Hour) // disconnect: long timer, will not fire - gc.Cancel(nodeID) // reconnect: must reap the watcher - } - - assert.EventuallyWithT(t, func(c *assert.CollectT) { - assert.LessOrEqual(c, runtime.NumGoroutine(), baseline+10, - "per-node goroutines leaked on Cancel/reschedule") - }, 2*time.Second, 20*time.Millisecond, "watcher goroutines should be reaped") -} - -// TestEphemeralGarbageCollectorCancelBeatsQueuedDeletion verifies that a node -// reconnecting (Cancel) after its deletion has already been queued on the -// internal channel is not deleted. The timer fires and enqueues the deletion; -// Cancel then runs before Start drains it. Start must drop the now-superseded -// deletion rather than removing the freshly reconnected node. -func TestEphemeralGarbageCollectorCancelBeatsQueuedDeletion(t *testing.T) { - const targetNode types.NodeID = 42 - - var ( - mu sync.Mutex - deleted []types.NodeID - ) - - e := NewEphemeralGarbageCollector(func(ni types.NodeID) { - mu.Lock() - defer mu.Unlock() - - deleted = append(deleted, ni) - }) - - // Schedule with a tiny expiry but do not drain yet: the watcher fires and - // enqueues the deletion onto the buffered channel. - e.Schedule(targetNode, time.Millisecond) - require.Eventually(t, func() bool { - return len(e.deleteCh) == 1 - }, time.Second, time.Millisecond, "deletion should be queued") - - // Node reconnects before the queue is drained. - e.Cancel(targetNode) - - go e.Start() - defer e.Close() - - require.Eventually(t, func() bool { - return len(e.deleteCh) == 0 - }, time.Second, time.Millisecond, "Start should drain the queued deletion") - - assert.Never(t, func() bool { - mu.Lock() - defer mu.Unlock() - - return slices.Contains(deleted, targetNode) - }, 200*time.Millisecond, 10*time.Millisecond, - "cancelled node must not be deleted") -} - -// TestEphemeralGarbageCollectorReschedule is a test for the rescheduling of nodes in [EphemeralGarbageCollector]. -// It creates a new [EphemeralGarbageCollector], schedules a node for deletion with a longer expiry, +// TestEphemeralGarbageCollectorReschedule is a test for the rescheduling of nodes in EphemeralGarbageCollector(). +// It creates a new EphemeralGarbageCollector, schedules a node for deletion with a longer expiry, // and then reschedules it with a shorter expiry, and verifies that the node is deleted only once. func TestEphemeralGarbageCollectorReschedule(t *testing.T) { // Deletion tracking mechanism @@ -223,8 +145,8 @@ func TestEphemeralGarbageCollectorReschedule(t *testing.T) { deleteMutex.Unlock() } -// TestEphemeralGarbageCollectorCancelAndReschedule is a test for the cancellation and rescheduling of nodes in [EphemeralGarbageCollector]. -// It creates a new [EphemeralGarbageCollector], schedules a node for deletion, cancels it, and then reschedules it, +// TestEphemeralGarbageCollectorCancelAndReschedule is a test for the cancellation and rescheduling of nodes in EphemeralGarbageCollector(). +// It creates a new EphemeralGarbageCollector, schedules a node for deletion, cancels it, and then reschedules it, // and verifies that the node is deleted only once. func TestEphemeralGarbageCollectorCancelAndReschedule(t *testing.T) { // Deletion tracking mechanism @@ -292,8 +214,8 @@ func TestEphemeralGarbageCollectorCancelAndReschedule(t *testing.T) { deleteMutex.Unlock() } -// TestEphemeralGarbageCollectorCloseBeforeTimerFires is a test for the closing of the [EphemeralGarbageCollector] before the timer fires. -// It creates a new [EphemeralGarbageCollector], schedules a node for deletion, closes the GC, and verifies that the node is not deleted. +// TestEphemeralGarbageCollectorCloseBeforeTimerFires is a test for the closing of the EphemeralGarbageCollector before the timer fires. +// It creates a new EphemeralGarbageCollector, schedules a node for deletion, closes the GC, and verifies that the node is not deleted. func TestEphemeralGarbageCollectorCloseBeforeTimerFires(t *testing.T) { // Deletion tracking var ( @@ -342,7 +264,7 @@ func TestEphemeralGarbageCollectorCloseBeforeTimerFires(t *testing.T) { deleteMutex.Unlock() } -// TestEphemeralGarbageCollectorScheduleAfterClose verifies that calling [EphemeralGarbageCollector.Schedule] after [EphemeralGarbageCollector.Close] +// TestEphemeralGarbageCollectorScheduleAfterClose verifies that calling Schedule after Close // is a no-op and doesn't cause any panics, goroutine leaks, or other issues. func TestEphemeralGarbageCollectorScheduleAfterClose(t *testing.T) { // Count initial goroutines to check for leaks @@ -417,7 +339,7 @@ func TestEphemeralGarbageCollectorScheduleAfterClose(t *testing.T) { } // TestEphemeralGarbageCollectorConcurrentScheduleAndClose tests the behavior of the garbage collector -// when [EphemeralGarbageCollector.Schedule] and [EphemeralGarbageCollector.Close] are called concurrently from multiple goroutines. +// when Schedule and Close are called concurrently from multiple goroutines. func TestEphemeralGarbageCollectorConcurrentScheduleAndClose(t *testing.T) { // Count initial goroutines initialGoroutines := runtime.NumGoroutine() diff --git a/hscontrol/db/ip.go b/hscontrol/db/ip.go index fede11b7f..7402f4737 100644 --- a/hscontrol/db/ip.go +++ b/hscontrol/db/ip.go @@ -49,7 +49,7 @@ type IPAllocator struct { usedIPs netipx.IPSetBuilder } -// NewIPAllocator returns a new [IPAllocator] singleton which +// NewIPAllocator returns a new IPAllocator singleton which // can be used to hand out unique IP addresses within the // provided IPv4 and IPv6 prefix. It needs to be created // when headscale starts and needs to finish its read @@ -138,6 +138,9 @@ func NewIPAllocator( } func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) { + i.mu.Lock() + defer i.mu.Unlock() + var ( err error ret4 *netip.Addr @@ -145,17 +148,21 @@ func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) { ) if i.prefix4 != nil { - ret4, err = i.allocateNext(&i.prev4, i.prefix4) + ret4, err = i.next(i.prev4, i.prefix4) if err != nil { return nil, nil, fmt.Errorf("allocating IPv4 address: %w", err) } + + i.prev4 = *ret4 } if i.prefix6 != nil { - ret6, err = i.allocateNext(&i.prev6, i.prefix6) + ret6, err = i.next(i.prev6, i.prefix6) if err != nil { return nil, nil, fmt.Errorf("allocating IPv6 address: %w", err) } + + i.prev6 = *ret6 } return ret4, ret6, nil @@ -163,22 +170,11 @@ func (i *IPAllocator) Next() (*netip.Addr, *netip.Addr, error) { var ErrCouldNotAllocateIP = errors.New("failed to allocate IP") -// allocateNext allocates the next address from prefix under i.mu, advancing -// prev so a run of allocations (e.g. BackfillNodeIPs) does not rescan -// already-issued addresses, and so prev is read under the lock rather than in -// the caller's frame. -func (i *IPAllocator) allocateNext(prev *netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) { +func (i *IPAllocator) nextLocked(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) { i.mu.Lock() defer i.mu.Unlock() - ret, err := i.next(*prev, prefix) - if err != nil { - return nil, err - } - - *prev = *ret - - return ret, nil + return i.next(prev, prefix) } func (i *IPAllocator) next(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, error) { @@ -204,40 +200,30 @@ func (i *IPAllocator) next(prev netip.Addr, prefix *netip.Prefix) (*netip.Addr, return nil, err } - // Walk forward from the starting address until a free, non-reserved - // address inside the prefix is found. The random strategy only picks the - // starting point at random and then scans deterministically: this keeps - // the loop finite, so an exhausted prefix returns ErrCouldNotAllocateIP - // instead of re-drawing in-prefix addresses forever under i.mu. - start := ip for { - if prefix.Contains(ip) && !set.Contains(ip) && !isTailscaleReservedIP(ip) { - i.usedIPs.Add(ip) - - return &ip, nil + if !prefix.Contains(ip) { + return nil, ErrCouldNotAllocateIP } - ip = ip.Next() - - switch i.strategy { - case types.IPAllocationStrategySequential: - // Sequential allocation never wraps: walking past the end of - // the prefix means the pool is exhausted. - if !prefix.Contains(ip) { - return nil, ErrCouldNotAllocateIP - } - case types.IPAllocationStrategyRandom: - // Random allocation wraps within the prefix so every address is - // examined exactly once; returning to the start means the prefix - // is exhausted. - if !prefix.Contains(ip) { - ip = prefix.Masked().Addr() + // Check if the IP has already been allocated + // or if it is a IP reserved by Tailscale. + if set.Contains(ip) || isTailscaleReservedIP(ip) { + switch i.strategy { + case types.IPAllocationStrategySequential: + ip = ip.Next() + case types.IPAllocationStrategyRandom: + ip, err = randomNext(*prefix) + if err != nil { + return nil, fmt.Errorf("getting random IP: %w", err) + } } - if ip == start { - return nil, ErrCouldNotAllocateIP - } + continue } + + i.usedIPs.Add(ip) + + return &ip, nil } } @@ -255,12 +241,6 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) { // after. tempMax := big.NewInt(0).Sub(&to, &from) - // A single-address prefix (/32 or /128) has from == to, so tempMax is 0 and - // rand.Int would panic on a non-positive bound. Return the sole address. - if tempMax.Sign() <= 0 { - return fromIP, nil - } - out, err := rand.Int(rand.Reader, tempMax) if err != nil { return netip.Addr{}, fmt.Errorf("generating random IP: %w", err) @@ -268,10 +248,7 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) { valInRange := big.NewInt(0).Add(&from, out) - // big.Int.Bytes() strips leading zero bytes, so a value with a zero high - // byte yields a too-short slice that AddrFromSlice rejects. Pad to the - // prefix's address width. - ip, ok := netip.AddrFromSlice(valInRange.FillBytes(make([]byte, len(fromIP.AsSlice())))) + ip, ok := netip.AddrFromSlice(valInRange.Bytes()) if !ok { return netip.Addr{}, errGeneratedIPBytesInvalid } @@ -295,7 +272,7 @@ func isTailscaleReservedIP(ip netip.Addr) bool { } // BackfillNodeIPs will take a database transaction, and -// iterate through all of the current nodes ([types.Node]) in headscale +// iterate through all of the current nodes in headscale // and ensure it has IP addresses according to the current // configuration. // This means that if both IPv4 and IPv6 is set in the @@ -327,7 +304,7 @@ func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) { changed := false // IPv4 prefix is set, but node ip is missing, alloc if i.prefix4 != nil && node.IPv4 == nil { - ret4, err := i.allocateNext(&i.prev4, i.prefix4) + ret4, err := i.nextLocked(i.prev4, i.prefix4) if err != nil { return fmt.Errorf("allocating IPv4 for node(%d): %w", node.ID, err) } @@ -340,7 +317,7 @@ func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) { // IPv6 prefix is set, but node ip is missing, alloc if i.prefix6 != nil && node.IPv6 == nil { - ret6, err := i.allocateNext(&i.prev6, i.prefix6) + ret6, err := i.nextLocked(i.prev6, i.prefix6) if err != nil { return fmt.Errorf("allocating IPv6 for node(%d): %w", node.ID, err) } @@ -369,6 +346,7 @@ func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) { // Use Updates() with Select() to only update IP fields, avoiding overwriting // other fields like Expiry. We need Select() because Updates() alone skips // zero values, but we DO want to update IPv4/IPv6 to nil when removing them. + // See issue #2862. err := tx.Model(node).Select("ipv4", "ipv6").Updates(node).Error if err != nil { return fmt.Errorf("saving node(%d) after adding IPs: %w", node.ID, err) diff --git a/hscontrol/db/ip_backfill_race_test.go b/hscontrol/db/ip_backfill_race_test.go deleted file mode 100644 index 25c7ad197..000000000 --- a/hscontrol/db/ip_backfill_race_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package db - -import ( - "net/netip" - "sync" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" -) - -// TestAllocatorConcurrentNextAndBackfillNoRace exercises the registration path -// (Next) concurrently with the backfill allocation path (allocateNext) on -// the same allocator. Backfill used to read prev4/prev6 in the caller's frame -// without the lock, racing Next's writes; both must now take i.mu. Run with -// -race. -func TestAllocatorConcurrentNextAndBackfillNoRace(t *testing.T) { - p4 := netip.MustParsePrefix("100.64.0.0/10") - p6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - alloc, err := NewIPAllocator(nil, &p4, &p6, types.IPAllocationStrategySequential) - require.NoError(t, err) - - const iterations = 2000 - - var wg sync.WaitGroup - - wg.Go(func() { - for range iterations { - _, _, err := alloc.Next() - if err != nil { - return - } - } - }) - - wg.Go(func() { - for range iterations { - _, err := alloc.allocateNext(&alloc.prev4, alloc.prefix4) - if err != nil { - return - } - - _, err = alloc.allocateNext(&alloc.prev6, alloc.prefix6) - if err != nil { - return - } - } - }) - - wg.Wait() -} diff --git a/hscontrol/db/ip_random_exhaustion_test.go b/hscontrol/db/ip_random_exhaustion_test.go deleted file mode 100644 index 350ad9ba8..000000000 --- a/hscontrol/db/ip_random_exhaustion_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package db - -import ( - "errors" - "net/netip" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/types" -) - -// TestIPAllocatorRandomExhaustionReturnsError ensures the random allocation -// strategy terminates when its prefix is exhausted. randomNext only ever -// returns in-prefix addresses, so the allocation loop's exhaustion exit must -// not depend on producing an out-of-prefix candidate; otherwise Next() spins -// forever under the allocator mutex, wedging all registration and IP release. -// -// 100.64.0.0/30 has four addresses: .0 (network) and .3 (broadcast) are -// reserved, leaving .1 and .2. After two allocations the pool is exhausted and -// the third Next() must return ErrCouldNotAllocateIP promptly. -func TestIPAllocatorRandomExhaustionReturnsError(t *testing.T) { - prefix4 := netip.MustParsePrefix("100.64.0.0/30") - - alloc, err := NewIPAllocator(nil, &prefix4, nil, types.IPAllocationStrategyRandom) - if err != nil { - t.Fatalf("NewIPAllocator: %v", err) - } - - for i := range 2 { - _, _, err := alloc.Next() - if err != nil { - t.Fatalf("Next() #%d unexpectedly failed: %v", i+1, err) - } - } - - done := make(chan error, 1) - - go func() { - _, _, err := alloc.Next() - done <- err - }() - - select { - case err := <-done: - if !errors.Is(err, ErrCouldNotAllocateIP) { - t.Fatalf("expected ErrCouldNotAllocateIP on exhausted prefix, got: %v", err) - } - case <-time.After(2 * time.Second): - t.Fatal("Next() on an exhausted random-strategy prefix did not return " + - "within 2s; it is spinning forever under the allocator mutex") - } -} diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index 002bde590..e6bd9cf0d 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -1,11 +1,12 @@ package db import ( - "cmp" + "encoding/json" "errors" "fmt" "net/netip" "slices" + "sort" "strconv" "sync" "testing" @@ -16,6 +17,7 @@ import ( "github.com/juanfont/headscale/hscontrol/util/zlog/zf" "github.com/rs/zerolog/log" "gorm.io/gorm" + "tailscale.com/net/tsaddr" "tailscale.com/types/key" "tailscale.com/util/dnsname" ) @@ -31,15 +33,6 @@ const ( // ErrNodeNameNotUnique is returned when a node name is not unique. var ErrNodeNameNotUnique = errors.New("node name is not unique") -// preloadNode returns a session that eager-loads a node's AuthKey, the -// AuthKey's User, and the node's User. -func preloadNode(tx *gorm.DB) *gorm.DB { - return tx. - Preload("AuthKey"). - Preload("AuthKey.User"). - Preload("User") -} - var ( ErrNodeNotFound = errors.New("node not found") ErrNodeRouteIsNotAvailable = errors.New("route is not available on node") @@ -62,14 +55,17 @@ func (hsdb *HSDatabase) ListPeers(nodeID types.NodeID, peerIDs ...types.NodeID) func ListPeers(tx *gorm.DB, nodeID types.NodeID, peerIDs ...types.NodeID) (types.Nodes, error) { nodes := types.Nodes{} - err := preloadNode(tx). + err := tx. + Preload("AuthKey"). + Preload("AuthKey.User"). + Preload("User"). Where("id <> ?", nodeID). Where(peerIDs).Find(&nodes).Error if err != nil { return types.Nodes{}, err } - slices.SortFunc(nodes, func(a, b *types.Node) int { return cmp.Compare(a.ID, b.ID) }) + sort.Slice(nodes, func(i, j int) bool { return nodes[i].ID < nodes[j].ID }) return nodes, nil } @@ -85,7 +81,10 @@ func (hsdb *HSDatabase) ListNodes(nodeIDs ...types.NodeID) (types.Nodes, error) func ListNodes(tx *gorm.DB, nodeIDs ...types.NodeID) (types.Nodes, error) { nodes := types.Nodes{} - err := preloadNode(tx). + err := tx. + Preload("AuthKey"). + Preload("AuthKey.User"). + Preload("User"). Where(nodeIDs).Find(&nodes).Error if err != nil { return nil, err @@ -113,35 +112,55 @@ func (hsdb *HSDatabase) getNode(uid types.UserID, name string) (*types.Node, err }) } -// getNode finds a [types.Node] by name and user and returns the [types.Node] struct. +// getNode finds a Node by name and user and returns the Node struct. func getNode(tx *gorm.DB, uid types.UserID, name string) (*types.Node, error) { - uidPtr := uint(uid) - - node := types.Node{} - - err := preloadNode(tx). - Where(&types.Node{UserID: &uidPtr, Hostname: name}). - First(&node).Error + nodes, err := ListNodesByUser(tx, uid) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrNodeNotFound - } - return nil, err } - return &node, nil + for _, m := range nodes { + if m.Hostname == name { + return m, nil + } + } + + return nil, ErrNodeNotFound } func (hsdb *HSDatabase) GetNodeByID(id types.NodeID) (*types.Node, error) { return GetNodeByID(hsdb.DB, id) } -// GetNodeByID finds a [types.Node] by ID and returns the [types.Node] struct. +// GetNodeByID finds a Node by ID and returns the Node struct. func GetNodeByID(tx *gorm.DB, id types.NodeID) (*types.Node, error) { mach := types.Node{} - if result := preloadNode(tx). - First(&mach, "id = ?", id); result.Error != nil { + if result := tx. + Preload("AuthKey"). + Preload("AuthKey.User"). + Preload("User"). + Find(&types.Node{ID: id}).First(&mach); result.Error != nil { + return nil, result.Error + } + + return &mach, nil +} + +func (hsdb *HSDatabase) GetNodeByMachineKey(machineKey key.MachinePublic) (*types.Node, error) { + return GetNodeByMachineKey(hsdb.DB, machineKey) +} + +// GetNodeByMachineKey finds a Node by its MachineKey and returns the Node struct. +func GetNodeByMachineKey( + tx *gorm.DB, + machineKey key.MachinePublic, +) (*types.Node, error) { + mach := types.Node{} + if result := tx. + Preload("AuthKey"). + Preload("AuthKey.User"). + Preload("User"). + First(&mach, "machine_key = ?", machineKey.String()); result.Error != nil { return nil, result.Error } @@ -152,13 +171,16 @@ func (hsdb *HSDatabase) GetNodeByNodeKey(nodeKey key.NodePublic) (*types.Node, e return GetNodeByNodeKey(hsdb.DB, nodeKey) } -// GetNodeByNodeKey finds a [types.Node] by its [key.NodePublic] and returns the [types.Node] struct. +// GetNodeByNodeKey finds a Node by its NodeKey and returns the Node struct. func GetNodeByNodeKey( tx *gorm.DB, nodeKey key.NodePublic, ) (*types.Node, error) { mach := types.Node{} - if result := preloadNode(tx). + if result := tx. + Preload("AuthKey"). + Preload("AuthKey.User"). + Preload("User"). First(&mach, "node_key = ?", nodeKey.String()); result.Error != nil { return nil, result.Error } @@ -166,6 +188,87 @@ func GetNodeByNodeKey( return &mach, nil } +func (hsdb *HSDatabase) SetTags( + nodeID types.NodeID, + tags []string, +) error { + return hsdb.Write(func(tx *gorm.DB) error { + return SetTags(tx, nodeID, tags) + }) +} + +// SetTags takes a NodeID and update the forced tags. +// It will overwrite any tags with the new list. +func SetTags( + tx *gorm.DB, + nodeID types.NodeID, + tags []string, +) error { + if len(tags) == 0 { + // if no tags are provided, we remove all tags + err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("tags", "[]").Error + if err != nil { + return fmt.Errorf("removing tags: %w", err) + } + + return nil + } + + slices.Sort(tags) + tags = slices.Compact(tags) + + b, err := json.Marshal(tags) + if err != nil { + return err + } + + err = tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("tags", string(b)).Error + if err != nil { + return fmt.Errorf("updating tags: %w", err) + } + + return nil +} + +// SetApprovedRoutes takes a Node struct pointer and updates the approved routes. +func SetApprovedRoutes( + tx *gorm.DB, + nodeID types.NodeID, + routes []netip.Prefix, +) error { + if len(routes) == 0 { + // if no routes are provided, we remove all + err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("approved_routes", "[]").Error + if err != nil { + return fmt.Errorf("removing approved routes: %w", err) + } + + return nil + } + + // When approving exit routes, ensure both IPv4 and IPv6 are included + // If either 0.0.0.0/0 or ::/0 is being approved, both should be approved + hasIPv4Exit := slices.Contains(routes, tsaddr.AllIPv4()) + hasIPv6Exit := slices.Contains(routes, tsaddr.AllIPv6()) + + if hasIPv4Exit && !hasIPv6Exit { + routes = append(routes, tsaddr.AllIPv6()) + } else if hasIPv6Exit && !hasIPv4Exit { + routes = append(routes, tsaddr.AllIPv4()) + } + + b, err := json.Marshal(routes) + if err != nil { + return err + } + + if err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("approved_routes", string(b)).Error; err != nil { //nolint:noinlineerr + return fmt.Errorf("updating approved routes: %w", err) + } + + return nil +} + // SetLastSeen sets a node's last seen field indicating that we // have recently communicating with this node. func (hsdb *HSDatabase) SetLastSeen(nodeID types.NodeID, lastSeen time.Time) error { @@ -180,7 +283,7 @@ func SetLastSeen(tx *gorm.DB, nodeID types.NodeID, lastSeen time.Time) error { return tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("last_seen", lastSeen).Error } -// RenameNode takes a [types.Node] struct and a new [types.Node.GivenName] for the nodes +// RenameNode takes a Node struct and a new GivenName for the nodes // and renames it. Validation should be done in the state layer before calling this function. func RenameNode(tx *gorm.DB, nodeID types.NodeID, newName string, @@ -226,7 +329,7 @@ func (hsdb *HSDatabase) DeleteNode(node *types.Node) error { }) } -// DeleteNode deletes a [types.Node] from the database. +// DeleteNode deletes a Node from the database. // Caller is responsible for notifying all of change. func DeleteNode(tx *gorm.DB, node *types.Node, @@ -240,7 +343,7 @@ func DeleteNode(tx *gorm.DB, return nil } -// DeleteEphemeralNode deletes a [types.Node] from the database, note that this method +// DeleteEphemeralNode deletes a Node from the database, note that this method // will remove it straight, and not notify any changes or consider any routes. // It is intended for Ephemeral nodes. func (hsdb *HSDatabase) DeleteEphemeralNode( @@ -257,7 +360,7 @@ func (hsdb *HSDatabase) DeleteEphemeralNode( } // RegisterNodeForTest is used only for testing purposes to register a node directly in the database. -// Production code should use [state.State.HandleNodeFromAuthPath] or [state.State.HandleNodeFromPreAuthKey]. +// Production code should use state.HandleNodeFromAuthPath or state.HandleNodeFromPreAuthKey. func RegisterNodeForTest(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Addr) (*types.Node, error) { if !testing.Testing() { panic("RegisterNodeForTest can only be called during tests") @@ -278,16 +381,12 @@ func RegisterNodeForTest(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *n logEvent.Msg("registering test node") - // Reuse the existing node's identity only when the same machine - // re-registers for the same user; a different user is a new node. Match on - // (machine_key, user_id) precisely - a machine key can map to several nodes - // (one per user), so a machine-key-only lookup would be ambiguous. - var oldNode types.Node - - err := tx. - Where("machine_key = ? AND user_id = ?", node.MachineKey.String(), node.UserID). - First(&oldNode).Error - if err == nil { + // If the a new node is registered with the same machine key, to the same user, + // update the existing node. + // If the same node is registered again, but to a new user, then that is considered + // a new node. + oldNode, _ := GetNodeByMachineKey(tx, node.MachineKey) + if oldNode != nil && oldNode.UserID == node.UserID { node.ID = oldNode.ID node.GivenName = oldNode.GivenName node.ApprovedRoutes = oldNode.ApprovedRoutes @@ -372,45 +471,24 @@ func NodeSetMachineKey( // EphemeralGarbageCollector is a garbage collector that will delete nodes after // a certain amount of time. -// It is used to delete ephemeral nodes ([types.Node.IsEphemeral]) that have disconnected and should be +// It is used to delete ephemeral nodes that have disconnected and should be // cleaned up. type EphemeralGarbageCollector struct { mu sync.Mutex deleteFunc func(types.NodeID) - toBeDeleted map[types.NodeID]ephemeralTimer - // gen is bumped for every scheduled deletion so a queued deletion that - // was superseded by a Cancel or reschedule can be recognised and dropped. - gen uint64 + toBeDeleted map[types.NodeID]*time.Timer - deleteCh chan pendingDeletion + deleteCh chan types.NodeID cancelCh chan struct{} } -// ephemeralTimer pairs a node's pending-deletion timer with a done channel -// used to reap its watcher goroutine on Cancel or reschedule, plus the -// generation identifying this particular scheduling. Without the done channel -// a stopped timer never fires and the goroutine leaks until Close. -type ephemeralTimer struct { - timer *time.Timer - done chan struct{} - gen uint64 -} - -// pendingDeletion is the generation-stamped deletion a watcher enqueues when -// its timer fires. Start drops it if the node's current generation no longer -// matches, i.e. it was cancelled or rescheduled in the meantime. -type pendingDeletion struct { - nodeID types.NodeID - gen uint64 -} - -// NewEphemeralGarbageCollector creates a new [EphemeralGarbageCollector], it takes +// NewEphemeralGarbageCollector creates a new EphemeralGarbageCollector, it takes // a deleteFunc that will be called when a node is scheduled for deletion. func NewEphemeralGarbageCollector(deleteFunc func(types.NodeID)) *EphemeralGarbageCollector { return &EphemeralGarbageCollector{ - toBeDeleted: make(map[types.NodeID]ephemeralTimer), - deleteCh: make(chan pendingDeletion, 10), + toBeDeleted: make(map[types.NodeID]*time.Timer), + deleteCh: make(chan types.NodeID, 10), cancelCh: make(chan struct{}), deleteFunc: deleteFunc, } @@ -422,8 +500,8 @@ func (e *EphemeralGarbageCollector) Close() { defer e.mu.Unlock() // Stop all timers - for _, t := range e.toBeDeleted { - t.timer.Stop() + for _, timer := range e.toBeDeleted { + timer.Stop() } // Close the cancel channel to signal all goroutines to exit @@ -446,18 +524,13 @@ func (e *EphemeralGarbageCollector) Schedule(nodeID types.NodeID, expiry time.Du // Continue with scheduling } - // If a timer already exists for this node, stop it and reap its - // watcher goroutine before scheduling a fresh one. - if old, exists := e.toBeDeleted[nodeID]; exists { - old.timer.Stop() - close(old.done) + // If a timer already exists for this node, stop it first + if oldTimer, exists := e.toBeDeleted[nodeID]; exists { + oldTimer.Stop() } - e.gen++ - gen := e.gen timer := time.NewTimer(expiry) - done := make(chan struct{}) - e.toBeDeleted[nodeID] = ephemeralTimer{timer: timer, done: done, gen: gen} + e.toBeDeleted[nodeID] = timer // Start a goroutine to handle the timer completion go func() { select { @@ -467,18 +540,12 @@ func (e *EphemeralGarbageCollector) Schedule(nodeID types.NodeID, expiry time.Du // i.e. We don't want to send to deleteCh if the GC is shutting down // So, we try to send to deleteCh, but also watch for cancelCh select { - case e.deleteCh <- pendingDeletion{nodeID: nodeID, gen: gen}: + case e.deleteCh <- nodeID: // Successfully sent to deleteCh case <-e.cancelCh: // GC is shutting down, don't send to deleteCh return - case <-done: - // Cancelled or rescheduled before the send landed. - return } - case <-done: - // Cancelled or rescheduled before the timer fired. - return case <-e.cancelCh: // If the GC is closed, exit the goroutine return @@ -491,9 +558,8 @@ func (e *EphemeralGarbageCollector) Cancel(nodeID types.NodeID) { e.mu.Lock() defer e.mu.Unlock() - if t, ok := e.toBeDeleted[nodeID]; ok { - t.timer.Stop() - close(t.done) + if timer, ok := e.toBeDeleted[nodeID]; ok { + timer.Stop() delete(e.toBeDeleted, nodeID) } } @@ -504,35 +570,16 @@ func (e *EphemeralGarbageCollector) Start() { select { case <-e.cancelCh: return - case pd := <-e.deleteCh: + case nodeID := <-e.deleteCh: e.mu.Lock() - - entry, ok := e.toBeDeleted[pd.nodeID] - if !ok || entry.gen != pd.gen { - // Cancelled or rescheduled after this deletion was queued; - // drop it so a reconnected node is not removed. - e.mu.Unlock() - - continue - } - - delete(e.toBeDeleted, pd.nodeID) + delete(e.toBeDeleted, nodeID) e.mu.Unlock() - go e.deleteFunc(pd.nodeID) + go e.deleteFunc(nodeID) } } } -// firstOr returns the first non-empty option, or def if none is provided. -func firstOr(def string, opt []string) string { - if len(opt) > 0 && opt[0] != "" { - return opt[0] - } - - return def -} - func (hsdb *HSDatabase) CreateNodeForTest(user *types.User, hostname ...string) *types.Node { if !testing.Testing() { panic("CreateNodeForTest can only be called during tests") @@ -542,7 +589,10 @@ func (hsdb *HSDatabase) CreateNodeForTest(user *types.User, hostname ...string) panic("CreateNodeForTest requires a valid user") } - nodeName := firstOr(defaultTestNodePrefix, hostname) + nodeName := defaultTestNodePrefix + if len(hostname) > 0 && hostname[0] != "" { + nodeName = hostname[0] + } // Create a preauth key for the node pak, err := hsdb.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) @@ -612,7 +662,10 @@ func (hsdb *HSDatabase) CreateNodesForTest(user *types.User, count int, hostname panic("CreateNodesForTest requires a valid user") } - prefix := firstOr(defaultTestNodePrefix, hostnamePrefix) + prefix := defaultTestNodePrefix + if len(hostnamePrefix) > 0 && hostnamePrefix[0] != "" { + prefix = hostnamePrefix[0] + } nodes := make([]*types.Node, count) for i := range count { @@ -632,7 +685,10 @@ func (hsdb *HSDatabase) CreateRegisteredNodesForTest(user *types.User, count int panic("CreateRegisteredNodesForTest requires a valid user") } - prefix := firstOr(defaultTestNodePrefix, hostnamePrefix) + prefix := defaultTestNodePrefix + if len(hostnamePrefix) > 0 && hostnamePrefix[0] != "" { + prefix = hostnamePrefix[0] + } nodes := make([]*types.Node, count) for i := range count { diff --git a/hscontrol/db/node_test.go b/hscontrol/db/node_test.go index b628bf71f..f9ba7b36e 100644 --- a/hscontrol/db/node_test.go +++ b/hscontrol/db/node_test.go @@ -178,6 +178,55 @@ func TestDisableNodeExpiry(t *testing.T) { assert.Nil(t, nodeFromDB.Expiry, "expiry should be nil after disabling") } +func TestSetTags(t *testing.T) { + db, err := newSQLiteTestDB() + require.NoError(t, err) + + user, err := db.CreateUser(types.User{Name: "test"}) + require.NoError(t, err) + + pak, err := db.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + pakID := pak.ID + + _, err = db.getNode(types.UserID(user.ID), "testnode") + require.Error(t, err) + + nodeKey := key.NewNode() + machineKey := key.NewMachine() + + node := &types.Node{ + ID: 0, + MachineKey: machineKey.Public(), + NodeKey: nodeKey.Public(), + Hostname: "testnode", + UserID: &user.ID, + RegisterMethod: util.RegisterMethodAuthKey, + AuthKeyID: &pakID, + } + + trx := db.DB.Save(node) + require.NoError(t, trx.Error) + + // assign simple tags + sTags := []string{"tag:test", "tag:foo"} + err = db.SetTags(node.ID, sTags) + require.NoError(t, err) + node, err = db.getNode(types.UserID(user.ID), "testnode") + require.NoError(t, err) + assert.Equal(t, sTags, node.Tags) + + // assign duplicate tags, expect no errors but no doubles in DB + eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"} + err = db.SetTags(node.ID, eTags) + require.NoError(t, err) + node, err = db.getNode(types.UserID(user.ID), "testnode") + require.NoError(t, err) + assert.Equal(t, []string{"tag:bar", "tag:test", "tag:unknown"}, node.Tags) +} + + func TestAutoApproveRoutes(t *testing.T) { tests := []struct { name string @@ -346,7 +395,7 @@ func TestAutoApproveRoutes(t *testing.T) { err = adb.DB.Save(&nodeTagged).Error require.NoError(t, err) - users, err := adb.ListUsers(nil) + users, err := adb.ListUsers() require.NoError(t, err) nodes, err := adb.ListNodes() @@ -360,15 +409,13 @@ func TestAutoApproveRoutes(t *testing.T) { assert.Equal(t, tt.expectChange, changed1) if changed1 { - node.ApprovedRoutes = types.Prefixes(newRoutes1) - err = adb.DB.Save(&node).Error + err = SetApprovedRoutes(adb.DB, node.ID, newRoutes1) require.NoError(t, err) } newRoutes2, changed2 := policy.ApproveRoutesWithPolicy(pm, nodeTagged.View(), nodeTagged.ApprovedRoutes, tt.routes) if changed2 { - nodeTagged.ApprovedRoutes = types.Prefixes(newRoutes2) - err = adb.DB.Save(&nodeTagged).Error + err = SetApprovedRoutes(adb.DB, nodeTagged.ID, newRoutes2) require.NoError(t, err) } @@ -578,6 +625,7 @@ func TestListEphemeralNodes(t *testing.T) { assert.Equal(t, nodeEph.Hostname, ephemeralNodes[0].Hostname) } + func TestListPeers(t *testing.T) { // Setup test database db, err := newSQLiteTestDB() diff --git a/hscontrol/db/oauth.go b/hscontrol/db/oauth.go deleted file mode 100644 index 6ce757bc7..000000000 --- a/hscontrol/db/oauth.go +++ /dev/null @@ -1,385 +0,0 @@ -package db - -import ( - "crypto/rand" - "crypto/subtle" - "encoding/base64" - "errors" - "fmt" - "runtime" - "slices" - "strings" - "time" - - "github.com/juanfont/headscale/hscontrol/types" - "golang.org/x/crypto/argon2" - "gorm.io/gorm" - "tailscale.com/util/rands" - "tailscale.com/util/set" -) - -const ( - // OAuth client secret: hskey-client--. The clientID - // is the public, indexed lookup key (the analogue of an API key's prefix) and - // is embedded in the secret so the token endpoint can derive it. The prefix - // itself lives in the types package ([types.OAuthClientPrefix]). - oauthClientIDLength = 12 - oauthClientSecretLength = 64 - - // OAuth access token: hskey-oauthtok--. The distinct - // prefix (vs hskey-api- admin keys, [types.AccessTokenPrefix]) lets the auth - // middleware dispatch a scoped token from an all-access admin key alone. - accessTokenPrefixLength = 12 - accessTokenSecretLength = 64 -) - -var ( - ErrOAuthClientNotFound = fmt.Errorf("oauth client not found: %w", gorm.ErrRecordNotFound) - ErrOAuthClientFailedToParse = errors.New("failed to parse oauth client secret") - ErrOAuthClientRevoked = errors.New("oauth client revoked") - - ErrAccessTokenNotFound = fmt.Errorf("oauth access token not found: %w", gorm.ErrRecordNotFound) - ErrAccessTokenFailedToParse = errors.New("failed to parse oauth access token") - ErrAccessTokenExpired = errors.New("oauth access token expired") - ErrAccessTokenClientRevoked = errors.New("oauth access token issuing client revoked or deleted") - - errSecretHashMalformed = errors.New("malformed secret hash") - errSecretMismatch = errors.New("secret does not match hash") -) - -// Argon2id parameters, OWASP's minimum recommendation (19 MiB, 2 iterations, 1 -// lane). They are encoded into every stored hash, so raising them later still -// verifies credentials stored under the old cost. -const ( - argon2Time = 2 - argon2Memory = 19 * 1024 - argon2Threads = 1 - argon2KeyLen = 32 - argon2SaltLen = 16 -) - -// argon2Limiter bounds concurrent Argon2id computations. Each costs ~19 MiB and -// the unauthenticated OAuth token endpoint runs one per attempt, so an unbounded -// flood could exhaust memory. ponytail: a global semaphore sized to GOMAXPROCS; -// revisit only if credential hashing ever becomes a throughput bottleneck. -var argon2Limiter = make(chan struct{}, max(2, runtime.GOMAXPROCS(0))) - -// hashSecret hashes a credential secret with Argon2id, encoded in PHC string -// form so the parameters travel with the hash. Argon2id is the current OWASP -// recommendation, replacing bcrypt for new credential storage. -func hashSecret(secret string) ([]byte, error) { - salt := make([]byte, argon2SaltLen) - - _, err := rand.Read(salt) - if err != nil { - return nil, fmt.Errorf("generating salt: %w", err) - } - - hash := argon2.IDKey([]byte(secret), salt, argon2Time, argon2Memory, argon2Threads, argon2KeyLen) - - encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", - argon2.Version, argon2Memory, argon2Time, argon2Threads, - base64.RawStdEncoding.EncodeToString(salt), - base64.RawStdEncoding.EncodeToString(hash), - ) - - return []byte(encoded), nil -} - -// verifySecret reports whether secret matches a hashSecret-encoded hash. It -// reads the cost parameters from the stored hash and compares in constant time -// so a mismatch leaks no timing signal. -func verifySecret(encoded []byte, secret string) error { - parts := strings.Split(string(encoded), "$") - if len(parts) != 6 || parts[1] != "argon2id" { - return errSecretHashMalformed - } - - var version int - if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil || version != argon2.Version { //nolint:noinlineerr - return errSecretHashMalformed - } - - var ( - memory, time uint32 - threads uint8 - ) - - if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads); err != nil { //nolint:noinlineerr - return errSecretHashMalformed - } - - salt, err := base64.RawStdEncoding.DecodeString(parts[4]) - if err != nil { - return errSecretHashMalformed - } - - want, err := base64.RawStdEncoding.DecodeString(parts[5]) - if err != nil { - return errSecretHashMalformed - } - - argon2Limiter <- struct{}{} - //nolint:gosec // want is a 32-byte hash read back from storage, no overflow - got := argon2.IDKey([]byte(secret), salt, time, memory, threads, uint32(len(want))) - - <-argon2Limiter - - if subtle.ConstantTimeCompare(got, want) != 1 { - return errSecretMismatch - } - - return nil -} - -// CreateOAuthClient creates a new [types.OAuthClient] and returns the plaintext -// secret (shown ONCE) alongside the stored client. creatorUserID is the user who -// created it (informational), or nil. -func (hsdb *HSDatabase) CreateOAuthClient( - scopes, tags []string, - description string, - creatorUserID *uint, -) (string, *types.OAuthClient, error) { - tags, err := validateACLTags(tags) - if err != nil { - return "", nil, err - } - - scopes = set.SetOf(scopes).Slice() - slices.Sort(scopes) - - clientID := rands.HexString(oauthClientIDLength) - secret := rands.HexString(oauthClientSecretLength) - secretStr := types.OAuthClientPrefix + clientID + "-" + secret - - hash, err := hashSecret(secret) - if err != nil { - return "", nil, err - } - - now := time.Now().UTC() - client := types.OAuthClient{ - ClientID: clientID, - SecretHash: hash, - Scopes: scopes, - Tags: tags, - Description: description, - UserID: creatorUserID, - CreatedAt: &now, - } - - err = hsdb.Write(func(tx *gorm.DB) error { - return tx.Save(&client).Error - }) - if err != nil { - return "", nil, fmt.Errorf("saving oauth client: %w", err) - } - - return secretStr, &client, nil -} - -// AuthenticateOAuthClient validates a presented client secret and returns the -// matching, unrevoked [types.OAuthClient]. The client id is derived from the -// secret (its middle segment), so any separately-supplied client_id is -// redundant, matching Tailscale, where get-authkey passes a dummy id and the -// server derives the real one from the secret. -func (hsdb *HSDatabase) AuthenticateOAuthClient(secretStr string) (*types.OAuthClient, error) { - if secretStr == "" { - return nil, ErrOAuthClientFailedToParse - } - - // Tailscale allows the secret to carry optional ?key=value attributes when - // used directly as an auth key; strip them before parsing. - secretStr, _, _ = strings.Cut(secretStr, "?") - - _, rest, found := strings.Cut(secretStr, types.OAuthClientPrefix) - if !found { - return nil, ErrOAuthClientFailedToParse - } - - clientID, secret, err := parsePrefixedKey( - rest, - oauthClientIDLength, - oauthClientSecretLength, - ErrOAuthClientFailedToParse, - ) - if err != nil { - return nil, err - } - - var client types.OAuthClient - if err := hsdb.DB.First(&client, "client_id = ?", clientID).Error; err != nil { //nolint:noinlineerr - return nil, ErrOAuthClientNotFound - } - - if err := verifySecret(client.SecretHash, secret); err != nil { //nolint:noinlineerr - return nil, fmt.Errorf("invalid oauth client secret: %w", err) - } - - if client.Revoked != nil { - return nil, ErrOAuthClientRevoked - } - - return &client, nil -} - -// GetOAuthClientByClientID returns a [types.OAuthClient] by its public client id. -func (hsdb *HSDatabase) GetOAuthClientByClientID(clientID string) (*types.OAuthClient, error) { - var client types.OAuthClient - if result := hsdb.DB.First(&client, "client_id = ?", clientID); result.Error != nil { - return nil, result.Error - } - - return &client, nil -} - -// ListOAuthClients returns every [types.OAuthClient]. -func (hsdb *HSDatabase) ListOAuthClients() ([]types.OAuthClient, error) { - clients := []types.OAuthClient{} - - err := hsdb.DB.Find(&clients).Error - if err != nil { - return nil, err - } - - return clients, nil -} - -// RevokeOAuthClient deletes a client and all access tokens it issued. An unknown -// client id returns [ErrOAuthClientNotFound], so a repeated DELETE is a clean -// 404. Unlike pre-auth keys (which soft-revoke for node-registration history), an -// OAuth client has no such history and is removed outright, matching Tailscale. -func (hsdb *HSDatabase) RevokeOAuthClient(clientID string) error { - return hsdb.Write(func(tx *gorm.DB) error { - err := tx.Where("client_id = ?", clientID). - Delete(&types.OAuthAccessToken{}).Error - if err != nil { - return fmt.Errorf("deleting oauth access tokens: %w", err) - } - - res := tx.Where("client_id = ?", clientID).Delete(&types.OAuthClient{}) - if res.Error != nil { - return res.Error - } - - if res.RowsAffected == 0 { - return ErrOAuthClientNotFound - } - - return nil - }) -} - -// MintAccessToken stores a new [types.OAuthAccessToken] for clientID with the -// given (already narrowed) scopes/tags and expiration, returning the plaintext -// token (shown ONCE). -func (hsdb *HSDatabase) MintAccessToken( - clientID string, - scopes, tags []string, - expiration *time.Time, -) (string, *types.OAuthAccessToken, error) { - prefix := rands.HexString(accessTokenPrefixLength) - secret := rands.HexString(accessTokenSecretLength) - tokenStr := types.AccessTokenPrefix + prefix + "-" + secret - - hash, err := hashSecret(secret) - if err != nil { - return "", nil, err - } - - now := time.Now().UTC() - token := types.OAuthAccessToken{ - Prefix: prefix, - Hash: hash, - ClientID: clientID, - Scopes: scopes, - Tags: tags, - Expiration: expiration, - CreatedAt: &now, - } - - // Mint inside a transaction that re-checks the client still exists and is - // not revoked, so a mint cannot complete against a client being deleted. - err = hsdb.Write(func(tx *gorm.DB) error { - var client types.OAuthClient - - err := tx.First(&client, "client_id = ?", clientID).Error - if err != nil { - return ErrOAuthClientNotFound - } - - if client.Revoked != nil { - return ErrOAuthClientRevoked - } - - return tx.Save(&token).Error - }) - if err != nil { - return "", nil, fmt.Errorf("saving oauth access token: %w", err) - } - - return tokenStr, &token, nil -} - -// AuthenticateAccessToken validates a presented bearer token and returns the -// matching, unexpired [types.OAuthAccessToken] (carrying its granted scopes and -// tags). A non-nil error means the token is missing, malformed, or expired. -func (hsdb *HSDatabase) AuthenticateAccessToken(tokenStr string) (*types.OAuthAccessToken, error) { - if tokenStr == "" { - return nil, ErrAccessTokenFailedToParse - } - - _, rest, found := strings.Cut(tokenStr, types.AccessTokenPrefix) - if !found { - return nil, ErrAccessTokenFailedToParse - } - - prefix, secret, err := parsePrefixedKey( - rest, - accessTokenPrefixLength, - accessTokenSecretLength, - ErrAccessTokenFailedToParse, - ) - if err != nil { - return nil, err - } - - var token types.OAuthAccessToken - if err := hsdb.DB.First(&token, "prefix = ?", prefix).Error; err != nil { //nolint:noinlineerr - return nil, ErrAccessTokenNotFound - } - - if err := verifySecret(token.Hash, secret); err != nil { //nolint:noinlineerr - return nil, fmt.Errorf("invalid oauth access token: %w", err) - } - - if token.Expiration != nil && token.Expiration.Before(time.Now()) { - return nil, ErrAccessTokenExpired - } - - // Bind validity to the issuing client: a token whose client has been - // revoked or deleted is rejected. This closes a mint/revoke race (where a - // token could be inserted after the client's tokens were purged) and any - // orphan left by manual deletion or a future soft-revoke path. - var client types.OAuthClient - if err := hsdb.DB.First(&client, "client_id = ?", token.ClientID).Error; err != nil { //nolint:noinlineerr - return nil, ErrAccessTokenClientRevoked - } - - if client.Revoked != nil { - return nil, ErrAccessTokenClientRevoked - } - - return &token, nil -} - -// DeleteExpiredAccessTokens hard-deletes every access token that expired before -// cutoff, returning how many were removed. Auth-time checks already reject -// expired tokens; the hourly reaper (see app.go) calls this only to keep the -// table from growing unbounded. -func (hsdb *HSDatabase) DeleteExpiredAccessTokens(cutoff time.Time) (int64, error) { - res := hsdb.DB.Where("expiration IS NOT NULL AND expiration < ?", cutoff). - Delete(&types.OAuthAccessToken{}) - - return res.RowsAffected, res.Error -} diff --git a/hscontrol/db/oauth_test.go b/hscontrol/db/oauth_test.go deleted file mode 100644 index 1bf8c72d4..000000000 --- a/hscontrol/db/oauth_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package db - -import ( - "strings" - "sync" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestVerifySecretConcurrent runs more concurrent verifications than the Argon2 -// concurrency semaphore admits, asserting the limiter releases correctly (no -// deadlock) and stays correct under contention. Run with -race. -func TestVerifySecretConcurrent(t *testing.T) { - hash, err := hashSecret("s3cr3t") - require.NoError(t, err) - - const n = 64 - - var wg sync.WaitGroup - - errs := make([]error, n) - - for i := range n { - wg.Add(1) - - go func(i int) { - defer wg.Done() - - if i%2 == 0 { - errs[i] = verifySecret(hash, "s3cr3t") - } else { - errs[i] = verifySecret(hash, "wrong") - } - }(i) - } - - wg.Wait() - - for i, e := range errs { - if i%2 == 0 { - assert.NoError(t, e, "correct secret must verify") - } else { - assert.Error(t, e, "wrong secret must fail") - } - } -} - -func TestOAuthClientCreateAndAuthenticate(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - secret, client, err := db.CreateOAuthClient( - []string{"auth_keys", "devices:core"}, - []string{"tag:ci"}, - "my client", - nil, - ) - require.NoError(t, err) - require.NotNil(t, client) - - // Secret carries the public client id as its middle segment, so it can be - // derived from the secret alone (the Tailscale get-authkey trick). - assert.True(t, strings.HasPrefix(secret, "hskey-client-"+client.ClientID+"-")) - // Scopes/tags are deduplicated and sorted for stable storage. - assert.Equal(t, []string{"auth_keys", "devices:core"}, client.Scopes) - assert.Equal(t, []string{"tag:ci"}, client.Tags) - // Only the Argon2id hash is stored, never the plaintext. - assert.NotEmpty(t, client.SecretHash) - assert.True(t, strings.HasPrefix(string(client.SecretHash), "$argon2id$")) - - // The secret authenticates, deriving the client id from the secret itself. - got, err := db.AuthenticateOAuthClient(secret) - require.NoError(t, err) - assert.Equal(t, client.ClientID, got.ClientID) - - // A truncated/garbage secret does not. - _, err = db.AuthenticateOAuthClient("hskey-client-deadbeef-nope") - require.Error(t, err) - - // Wrong secret for a real client id is rejected by the constant-time compare. - _, err = db.AuthenticateOAuthClient("hskey-client-" + client.ClientID + "-" + strings.Repeat("0", 64)) - require.Error(t, err) -} - -func TestHashSecretRoundTrip(t *testing.T) { - const secret = "a-high-entropy-credential-secret" - - encoded, err := hashSecret(secret) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(string(encoded), "$argon2id$v=")) - - // The same secret hashes to a different value each time (random salt) yet - // still verifies. - encoded2, err := hashSecret(secret) - require.NoError(t, err) - assert.NotEqual(t, encoded, encoded2) - - require.NoError(t, verifySecret(encoded, secret)) - require.ErrorIs(t, verifySecret(encoded, "wrong-secret"), errSecretMismatch) - require.ErrorIs(t, verifySecret([]byte("not-a-phc-string"), secret), errSecretHashMalformed) -} - -func TestOAuthClientRevoke(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - secret, client, err := db.CreateOAuthClient([]string{"auth_keys"}, []string{"tag:ci"}, "", nil) - require.NoError(t, err) - - // A token minted by the client survives only until the client is revoked. - _, _, err = db.MintAccessToken(client.ClientID, client.Scopes, client.Tags, nil) - require.NoError(t, err) - - require.NoError(t, db.RevokeOAuthClient(client.ClientID)) - - // The client no longer authenticates and a repeated revoke is a clean 404. - _, err = db.AuthenticateOAuthClient(secret) - require.Error(t, err) - require.ErrorIs(t, db.RevokeOAuthClient(client.ClientID), ErrOAuthClientNotFound) -} - -func TestOAuthAccessTokenMintAuthenticateExpire(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - _, client, err := db.CreateOAuthClient([]string{"auth_keys"}, []string{"tag:ci"}, "", nil) - require.NoError(t, err) - - future := time.Now().Add(time.Hour) - tokenStr, token, err := db.MintAccessToken( - client.ClientID, - []string{"auth_keys"}, - []string{"tag:ci"}, - &future, - ) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(tokenStr, "hskey-oauthtok-")) - - got, err := db.AuthenticateAccessToken(tokenStr) - require.NoError(t, err) - assert.Equal(t, client.ClientID, got.ClientID) - assert.Equal(t, []string{"auth_keys"}, got.Scopes) - assert.Equal(t, []string{"tag:ci"}, got.Tags) - - // An expired token is rejected even though the row still exists. - past := time.Now().Add(-time.Hour) - expiredStr, _, err := db.MintAccessToken(client.ClientID, nil, nil, &past) - require.NoError(t, err) - _, err = db.AuthenticateAccessToken(expiredStr) - require.ErrorIs(t, err, ErrAccessTokenExpired) - - // The reaper deletes the expired row; the live token is untouched. - n, err := db.DeleteExpiredAccessTokens(time.Now()) - require.NoError(t, err) - assert.Equal(t, int64(1), n) - - _ = token - - _, err = db.AuthenticateAccessToken(tokenStr) - require.NoError(t, err) -} - -// TestAccessTokenRejectedWhenClientGone asserts a token whose issuing client no -// longer exists (orphaned by a delete/revoke race) is rejected, even though the -// token row itself is valid and unexpired. -func TestAccessTokenRejectedWhenClientGone(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - _, client, err := db.CreateOAuthClient([]string{"auth_keys"}, []string{"tag:ci"}, "", nil) - require.NoError(t, err) - - future := time.Now().Add(time.Hour) - tokenStr, _, err := db.MintAccessToken(client.ClientID, []string{"auth_keys"}, []string{"tag:ci"}, &future) - require.NoError(t, err) - - _, err = db.AuthenticateAccessToken(tokenStr) - require.NoError(t, err) - - // Delete only the client row, leaving the token orphaned (the state a - // mint/revoke race or manual deletion would produce). - require.NoError(t, db.DB.Where("client_id = ?", client.ClientID).Delete(&types.OAuthClient{}).Error) - - _, err = db.AuthenticateAccessToken(tokenStr) - require.ErrorIs(t, err, ErrAccessTokenClientRevoked) - - // A soft-revoked client (row present, Revoked set) is likewise rejected. - _, client2, err := db.CreateOAuthClient([]string{"auth_keys"}, []string{"tag:ci"}, "", nil) - require.NoError(t, err) - - tokenStr2, _, err := db.MintAccessToken(client2.ClientID, []string{"auth_keys"}, []string{"tag:ci"}, &future) - require.NoError(t, err) - - now := time.Now() - require.NoError(t, db.DB.Model(&types.OAuthClient{}). - Where("client_id = ?", client2.ClientID).Update("revoked", now).Error) - - _, err = db.AuthenticateAccessToken(tokenStr2) - require.ErrorIs(t, err, ErrAccessTokenClientRevoked) -} diff --git a/hscontrol/db/policy.go b/hscontrol/db/policy.go index 3c60534d6..83bb48122 100644 --- a/hscontrol/db/policy.go +++ b/hscontrol/db/policy.go @@ -31,7 +31,7 @@ func (hsdb *HSDatabase) GetPolicy() (*types.Policy, error) { } // GetPolicy returns the latest policy from the database. -// This standalone function can be used in contexts where [HSDatabase] is not available, +// This standalone function can be used in contexts where HSDatabase is not available, // such as during migrations. func GetPolicy(tx *gorm.DB) (*types.Policy, error) { var p types.Policy @@ -55,7 +55,7 @@ func GetPolicy(tx *gorm.DB) (*types.Policy, error) { // PolicyBytes loads policy configuration from file or database based on the configured mode. // Returns nil if no policy is configured, which is valid. -// This standalone function can be used in contexts where [HSDatabase] is not available, +// This standalone function can be used in contexts where HSDatabase is not available, // such as during migrations. func PolicyBytes(tx *gorm.DB, cfg *types.Config) ([]byte, error) { switch cfg.Policy.Mode { diff --git a/hscontrol/db/preauth_keys.go b/hscontrol/db/preauth_keys.go index e839ff507..d2fb0265a 100644 --- a/hscontrol/db/preauth_keys.go +++ b/hscontrol/db/preauth_keys.go @@ -8,43 +8,20 @@ import ( "time" "github.com/juanfont/headscale/hscontrol/types" + "github.com/juanfont/headscale/hscontrol/util" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" - "tailscale.com/util/rands" "tailscale.com/util/set" ) var ( - // ErrPreAuthKeyNotFound wraps gorm.ErrRecordNotFound so an unknown or - // deleted key is treated as a missing record by callers, which the - // registration handler maps to a 401 rather than a raw server error. - ErrPreAuthKeyNotFound = fmt.Errorf("auth-key not found: %w", gorm.ErrRecordNotFound) + ErrPreAuthKeyNotFound = errors.New("auth-key not found") ErrPreAuthKeyExpired = errors.New("auth-key expired") ErrSingleUseAuthKeyHasBeenUsed = errors.New("auth-key has already been used") ErrUserMismatch = errors.New("user mismatch") ErrPreAuthKeyACLTagInvalid = errors.New("auth-key tag is invalid") ) -// validateACLTags deduplicates, sorts, and checks that every tag carries the -// "tag:" prefix. Shared by the pre-auth-key and OAuth credential paths so both -// enforce the same tag shape. -func validateACLTags(tags []string) ([]string, error) { - tags = set.SetOf(tags).Slice() - slices.Sort(tags) - - for _, tag := range tags { - if !strings.HasPrefix(tag, "tag:") { - return nil, fmt.Errorf( - "%w: '%s' did not begin with 'tag:'", - ErrPreAuthKeyACLTagInvalid, - tag, - ) - } - } - - return tags, nil -} - func (hsdb *HSDatabase) CreatePreAuthKey( uid *types.UserID, reusable bool, @@ -63,7 +40,7 @@ const ( authKeyLength = 64 ) -// CreatePreAuthKey creates a new [types.PreAuthKey] in a user, and returns it. +// CreatePreAuthKey creates a new PreAuthKey in a user, and returns it. // The uid parameter can be nil for system-created tagged keys. // For tagged keys, uid tracks "created by" (who created the key). // For user-owned keys, uid tracks the node owner. @@ -96,16 +73,51 @@ func CreatePreAuthKey( userID = &user.ID } - aclTags, err := validateACLTags(aclTags) - if err != nil { - return nil, err + // Remove duplicates and sort for consistency + aclTags = set.SetOf(aclTags).Slice() + slices.Sort(aclTags) + + // TODO(kradalby): factor out and create a reusable tag validation, + // check if there is one in Tailscale's lib. + for _, tag := range aclTags { + if !strings.HasPrefix(tag, "tag:") { + return nil, fmt.Errorf( + "%w: '%s' did not begin with 'tag:'", + ErrPreAuthKeyACLTagInvalid, + tag, + ) + } } now := time.Now().UTC() - prefix := rands.HexString(authKeyPrefixLength) + prefix, err := util.GenerateRandomStringURLSafe(authKeyPrefixLength) + if err != nil { + return nil, err + } - toBeHashed := rands.HexString(authKeyLength) + // Validate generated prefix (should always be valid, but be defensive) + if len(prefix) != authKeyPrefixLength { + return nil, fmt.Errorf("%w: generated prefix has invalid length: expected %d, got %d", ErrPreAuthKeyFailedToParse, authKeyPrefixLength, len(prefix)) + } + + if !isValidBase64URLSafe(prefix) { + return nil, fmt.Errorf("%w: generated prefix contains invalid characters", ErrPreAuthKeyFailedToParse) + } + + toBeHashed, err := util.GenerateRandomStringURLSafe(authKeyLength) + if err != nil { + return nil, err + } + + // Validate generated hash (should always be valid, but be defensive) + if len(toBeHashed) != authKeyLength { + return nil, fmt.Errorf("%w: generated hash has invalid length: expected %d, got %d", ErrPreAuthKeyFailedToParse, authKeyLength, len(toBeHashed)) + } + + if !isValidBase64URLSafe(toBeHashed) { + return nil, fmt.Errorf("%w: generated hash contains invalid characters", ErrPreAuthKeyFailedToParse) + } keyStr := authKeyPrefix + prefix + "-" + toBeHashed @@ -142,20 +154,11 @@ func CreatePreAuthKey( }, nil } -// SetPreAuthKeyDescription sets the free-text description on a pre-auth key. -// The v2 keys API sets it after creation rather than threading it through the -// many-armed CreatePreAuthKey signature shared by every other caller. -func (hsdb *HSDatabase) SetPreAuthKeyDescription(id uint64, description string) error { - return hsdb.DB.Model(&types.PreAuthKey{}). - Where("id = ?", id). - Update("description", description).Error -} - func (hsdb *HSDatabase) ListPreAuthKeys() ([]types.PreAuthKey, error) { return Read(hsdb.DB, ListPreAuthKeys) } -// ListPreAuthKeys returns all [types.PreAuthKey] values in the database. +// ListPreAuthKeys returns all PreAuthKeys in the database. func ListPreAuthKeys(tx *gorm.DB) ([]types.PreAuthKey, error) { var keys []types.PreAuthKey @@ -167,7 +170,7 @@ func ListPreAuthKeys(tx *gorm.DB) ([]types.PreAuthKey, error) { return keys, nil } -// ListPreAuthKeysByUser returns all [types.PreAuthKey] values belonging to a specific user. +// ListPreAuthKeysByUser returns all PreAuthKeys belonging to a specific user. func ListPreAuthKeysByUser(tx *gorm.DB, uid types.UserID) ([]types.PreAuthKey, error) { var keys []types.PreAuthKey @@ -205,18 +208,60 @@ func findAuthKey(tx *gorm.DB, keyStr string) (*types.PreAuthKey, error) { } // New format: hskey-auth-{12-char-prefix}-{64-char-hash} - prefix, hash, err := parsePrefixedKey( - prefixAndHash, - authKeyPrefixLength, - authKeyLength, - ErrPreAuthKeyFailedToParse, - ) - if err != nil { - return nil, err + // Expected minimum length: 12 (prefix) + 1 (separator) + 64 (hash) = 77 + const expectedMinLength = authKeyPrefixLength + 1 + authKeyLength + if len(prefixAndHash) < expectedMinLength { + return nil, fmt.Errorf( + "%w: key too short, expected at least %d chars after prefix, got %d", + ErrPreAuthKeyFailedToParse, + expectedMinLength, + len(prefixAndHash), + ) + } + + // Use fixed-length parsing instead of separator-based to handle dashes in base64 URL-safe + prefix := prefixAndHash[:authKeyPrefixLength] + + // Validate separator at expected position + if prefixAndHash[authKeyPrefixLength] != '-' { + return nil, fmt.Errorf( + "%w: expected separator '-' at position %d, got '%c'", + ErrPreAuthKeyFailedToParse, + authKeyPrefixLength, + prefixAndHash[authKeyPrefixLength], + ) + } + + hash := prefixAndHash[authKeyPrefixLength+1:] + + // Validate hash length + if len(hash) != authKeyLength { + return nil, fmt.Errorf( + "%w: hash length mismatch, expected %d chars, got %d", + ErrPreAuthKeyFailedToParse, + authKeyLength, + len(hash), + ) + } + + // Validate prefix contains only base64 URL-safe characters + if !isValidBase64URLSafe(prefix) { + return nil, fmt.Errorf( + "%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", + ErrPreAuthKeyFailedToParse, + ) + } + + // Validate hash contains only base64 URL-safe characters + if !isValidBase64URLSafe(hash) { + return nil, fmt.Errorf( + "%w: hash contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", + ErrPreAuthKeyFailedToParse, + ) } // Look up key by prefix - err = tx.Preload("User").First(&pak, "prefix = ?", prefix).Error + err := tx.Preload("User").First(&pak, "prefix = ?", prefix).Error if err != nil { return nil, ErrPreAuthKeyNotFound } @@ -230,105 +275,28 @@ func findAuthKey(tx *gorm.DB, keyStr string) (*types.PreAuthKey, error) { return &pak, nil } -// parsePrefixedKey splits the prefix-and-secret portion of a new-format key -// (the part after the "hskey-*-" prefix) into its fixed-length prefix and -// secret components, validating the length, separator position, and that both -// components are base64 URL-safe. Fixed-length parsing is used instead of -// separator-based to handle dashes in base64 URL-safe characters. -func parsePrefixedKey( - prefixAndSecret string, - //nolint:unparam // kept explicit though every credential kind uses a 12-char prefix and 64-char secret today - prefixLen, secretLen int, - parseErr error, -) (string, string, error) { - expectedMinLength := prefixLen + 1 + secretLen - if len(prefixAndSecret) < expectedMinLength { - return "", "", fmt.Errorf( - "%w: key too short, expected at least %d chars after prefix, got %d", - parseErr, - expectedMinLength, - len(prefixAndSecret), - ) - } - - prefix := prefixAndSecret[:prefixLen] - - // Validate separator at expected position - if prefixAndSecret[prefixLen] != '-' { - return "", "", fmt.Errorf( - "%w: expected separator '-' at position %d, got '%c'", - parseErr, - prefixLen, - prefixAndSecret[prefixLen], - ) - } - - secret := prefixAndSecret[prefixLen+1:] - - // Validate secret length - if len(secret) != secretLen { - return "", "", fmt.Errorf( - "%w: secret length mismatch, expected %d chars, got %d", - parseErr, - secretLen, - len(secret), - ) - } - - // Validate prefix contains only base64 URL-safe characters - if !isValidBase64URLSafe(prefix) { - return "", "", fmt.Errorf( - "%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", - parseErr, - ) - } - - // Validate secret contains only base64 URL-safe characters - if !isValidBase64URLSafe(secret) { - return "", "", fmt.Errorf( - "%w: secret contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)", - parseErr, - ) - } - - return prefix, secret, nil -} - -// isValidBase64URLSafe reports whether s contains only base64 URL-safe -// characters (A-Za-z0-9-_). Key material is now generated as hex, a subset of -// this alphabet, so this accepts both current hex keys and any legacy keys -// still stored in the database. +// isValidBase64URLSafe checks if a string contains only base64 URL-safe characters. func isValidBase64URLSafe(s string) bool { - return !strings.ContainsFunc(s, func(c rune) bool { - return (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '-' && c != '_' - }) + for _, c := range s { + if (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '-' && c != '_' { + return false + } + } + + return true } func (hsdb *HSDatabase) GetPreAuthKey(key string) (*types.PreAuthKey, error) { return GetPreAuthKey(hsdb.DB, key) } -// GetPreAuthKey returns a [types.PreAuthKey] for a given key. The caller is responsible +// GetPreAuthKey returns a PreAuthKey for a given key. The caller is responsible // for checking if the key is usable (expired or used). func GetPreAuthKey(tx *gorm.DB, key string) (*types.PreAuthKey, error) { return findAuthKey(tx, key) } -// GetPreAuthKeyByID returns a [types.PreAuthKey] by its primary key, with the -// owning user preloaded. -func (hsdb *HSDatabase) GetPreAuthKeyByID(id uint64) (*types.PreAuthKey, error) { - pak := types.PreAuthKey{} - // Explicit primary-key clause: a struct condition would drop a zero-valued - // ID, making the lookup unconditional and returning the first row instead - // of not-found. - if result := hsdb.DB.Preload("User").First(&pak, "id = ?", id); result.Error != nil { - return nil, result.Error - } - - return &pak, nil -} - -// DestroyPreAuthKey destroys a preauthkey. Returns error if the [types.PreAuthKey] +// DestroyPreAuthKey destroys a preauthkey. Returns error if the PreAuthKey // does not exist. This also clears the auth_key_id on any nodes that reference // this key. func DestroyPreAuthKey(tx *gorm.DB, id uint64) error { @@ -342,13 +310,9 @@ func DestroyPreAuthKey(tx *gorm.DB, id uint64) error { } // Then delete the pre-auth key - res := tx.Unscoped().Delete(&types.PreAuthKey{}, id) - if res.Error != nil { - return res.Error - } - - if res.RowsAffected == 0 { - return ErrPreAuthKeyNotFound + err = tx.Unscoped().Delete(&types.PreAuthKey{}, id).Error + if err != nil { + return err } return nil @@ -367,67 +331,10 @@ func (hsdb *HSDatabase) DeletePreAuthKey(id uint64) error { }) } -func (hsdb *HSDatabase) RevokePreAuthKey(id uint64) error { - return hsdb.Write(func(tx *gorm.DB) error { - return RevokePreAuthKey(tx, id) - }) -} - -// RevokePreAuthKey soft-revokes a key (the v2 API's DELETE): the row is kept and -// stays retrievable with its invalid flag set, but the key can no longer -// authorize nodes. The background collector hard-deletes it after the retention -// window. An already-revoked or unknown id returns [ErrPreAuthKeyNotFound], so a -// repeated DELETE is a clean 404. -func RevokePreAuthKey(tx *gorm.DB, id uint64) error { - res := tx.Model(&types.PreAuthKey{}). - Where("id = ? AND revoked IS NULL", id). - Update("revoked", time.Now()) - if res.Error != nil { - return res.Error - } - - if res.RowsAffected == 0 { - return ErrPreAuthKeyNotFound - } - - return nil -} - -// DestroyRevokedPreAuthKeysBefore hard-deletes every key revoked before cutoff, -// returning how many were removed. The background collector calls this to reap -// soft-revoked keys after the retention window. -func (hsdb *HSDatabase) DestroyRevokedPreAuthKeysBefore(cutoff time.Time) (int, error) { - var count int - - err := hsdb.Write(func(tx *gorm.DB) error { - var ids []uint64 - - err := tx.Model(&types.PreAuthKey{}). - Where("revoked IS NOT NULL AND revoked < ?", cutoff). - Pluck("id", &ids).Error - if err != nil { - return err - } - - for _, id := range ids { - err := DestroyPreAuthKey(tx, id) - if err != nil { - return err - } - } - - count = len(ids) - - return nil - }) - - return count, err -} - -// UsePreAuthKey atomically marks a [types.PreAuthKey] as used. The UPDATE is +// UsePreAuthKey atomically marks a PreAuthKey as used. The UPDATE is // guarded by `used = false` so two concurrent registrations racing for // the same single-use key cannot both succeed: the first commits and -// the second returns [types.PAKError]("authkey already used"). Without the +// the second returns PAKError("authkey already used"). Without the // guard the previous code (Update("used", true) with no WHERE) would // silently let both transactions claim the key. func UsePreAuthKey(tx *gorm.DB, k *types.PreAuthKey) error { @@ -447,19 +354,8 @@ func UsePreAuthKey(tx *gorm.DB, k *types.PreAuthKey) error { return nil } -// ExpirePreAuthKey marks a [types.PreAuthKey] as expired, returning -// [ErrPreAuthKeyNotFound] rather than succeeding silently when no such key exists. +// ExpirePreAuthKey marks a PreAuthKey as expired. func ExpirePreAuthKey(tx *gorm.DB, id uint64) error { now := time.Now() - - res := tx.Model(&types.PreAuthKey{}).Where("id = ?", id).Update("expiration", now) - if res.Error != nil { - return res.Error - } - - if res.RowsAffected == 0 { - return ErrPreAuthKeyNotFound - } - - return nil + return tx.Model(&types.PreAuthKey{}).Where("id = ?", id).Update("expiration", now).Error } diff --git a/hscontrol/db/preauth_keys_test.go b/hscontrol/db/preauth_keys_test.go index 5835758f3..25bec17e9 100644 --- a/hscontrol/db/preauth_keys_test.go +++ b/hscontrol/db/preauth_keys_test.go @@ -96,7 +96,7 @@ func TestPreAuthKeyACLTags(t *testing.T) { require.NoError(t, err) require.Len(t, listedPaks, 1) - gotTags := slices.Clone(listedPaks[0].Tags) + gotTags := listedPaks[0].Proto().GetAclTags() slices.Sort(gotTags) assert.Equal(t, expectedTags, gotTags) }, @@ -487,16 +487,3 @@ func TestUsePreAuthKeyAtomicCAS(t *testing.T) { "second UsePreAuthKey error must be a PAKError, got: %v", err) assert.Equal(t, "authkey already used", pakErr.Error()) } - -// TestGetPreAuthKeyUnknownMapsToRecordNotFound ensures an unknown (or deleted) -// pre-auth key resolves to a record-not-found error, which the registration -// handler maps to a 401 rather than a raw server error. -func TestGetPreAuthKeyUnknownMapsToRecordNotFound(t *testing.T) { - db, err := newSQLiteTestDB() - require.NoError(t, err) - - _, err = db.GetPreAuthKey("nonexistent-key") - require.Error(t, err) - require.ErrorIs(t, err, gorm.ErrRecordNotFound, - "unknown pre-auth key must map to record-not-found (handled as 401)") -} diff --git a/hscontrol/db/randomnext_test.go b/hscontrol/db/randomnext_test.go deleted file mode 100644 index d20ecf416..000000000 --- a/hscontrol/db/randomnext_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package db - -import ( - "net/netip" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestRandomNextSingleAddressPrefix ensures a single-address prefix (/32 or -// /128) does not panic. from == to makes the random range zero, and rand.Int -// panics on a non-positive bound; the sole address must be returned instead. -func TestRandomNextSingleAddressPrefix(t *testing.T) { - for _, p := range []string{"100.64.0.1/32", "fd7a:115c:a1e0::1/128"} { - pfx := netip.MustParsePrefix(p) - require.NotPanics(t, func() { - ip, err := randomNext(pfx) - require.NoError(t, err) - assert.Equal(t, pfx.Addr(), ip) - }, "prefix %s", p) - } -} - -// TestRandomNextLeadingZeroBytes ensures prefixes whose addresses have a zero -// high byte allocate successfully. big.Int.Bytes() strips leading zeros, so the -// drawn value would be too short for netip.AddrFromSlice without padding. -func TestRandomNextLeadingZeroBytes(t *testing.T) { - pfx := netip.MustParsePrefix("0.0.0.0/16") - for range 100 { - ip, err := randomNext(pfx) - require.NoError(t, err) - assert.True(t, pfx.Contains(ip), "ip %s not in %s", ip, pfx) - } -} diff --git a/hscontrol/db/schema.sql b/hscontrol/db/schema.sql index 59b601601..781446c00 100644 --- a/hscontrol/db/schema.sql +++ b/hscontrol/db/schema.sql @@ -44,13 +44,11 @@ CREATE TABLE pre_auth_keys( prefix text, hash blob, user_id integer, - description text, reusable numeric, ephemeral numeric DEFAULT false, used numeric DEFAULT false, tags text, expiration datetime, - revoked datetime, created_at datetime, @@ -62,7 +60,6 @@ CREATE TABLE api_keys( id integer PRIMARY KEY AUTOINCREMENT, prefix text, hash blob, - user_id integer, expiration datetime, last_seen datetime, @@ -70,36 +67,6 @@ CREATE TABLE api_keys( ); CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); --- OAuth 2.0 client-credentials clients for the v2 API. client_id is public and --- embedded in the secret (hskey-client--); only the bcrypt --- hash of the secret is stored. Mirrors the api_keys security model. -CREATE TABLE oauth_clients( - id integer PRIMARY KEY AUTOINCREMENT, - client_id text, - secret_hash blob, - scopes text, - tags text, - description text, - user_id integer, - created_at datetime, - revoked datetime -); -CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id); - --- Short-lived bearer access tokens minted by an oauth_client. Stored as a bcrypt --- hash of the secret, looked up by prefix. -CREATE TABLE oauth_access_tokens( - id integer PRIMARY KEY AUTOINCREMENT, - prefix text, - hash blob, - client_id text, - scopes text, - tags text, - expiration datetime, - created_at datetime -); -CREATE UNIQUE INDEX idx_oauth_access_tokens_prefix ON oauth_access_tokens(prefix); - CREATE TABLE nodes( id integer PRIMARY KEY AUTOINCREMENT, machine_key text, diff --git a/hscontrol/db/sqliteconfig/config.go b/hscontrol/db/sqliteconfig/config.go index 9d818ae1b..b4c807955 100644 --- a/hscontrol/db/sqliteconfig/config.go +++ b/hscontrol/db/sqliteconfig/config.go @@ -5,7 +5,6 @@ package sqliteconfig import ( "errors" "fmt" - "slices" "strings" ) @@ -91,15 +90,20 @@ const ( JournalModeOff JournalMode = "OFF" ) -// validJournalModes lists the accepted JournalMode values. -var validJournalModes = []JournalMode{ - JournalModeWAL, JournalModeDelete, JournalModeTruncate, - JournalModePersist, JournalModeMemory, JournalModeOff, -} - // IsValid returns true if the JournalMode is valid. func (j JournalMode) IsValid() bool { - return slices.Contains(validJournalModes, j) + switch j { + case JournalModeWAL, JournalModeDelete, JournalModeTruncate, + JournalModePersist, JournalModeMemory, JournalModeOff: + return true + default: + return false + } +} + +// String returns the string representation. +func (j JournalMode) String() string { + return string(j) } // AutoVacuum represents SQLite auto_vacuum pragma values. @@ -143,14 +147,19 @@ const ( AutoVacuumIncremental AutoVacuum = "INCREMENTAL" ) -// validAutoVacuums lists the accepted AutoVacuum values. -var validAutoVacuums = []AutoVacuum{ - AutoVacuumNone, AutoVacuumFull, AutoVacuumIncremental, -} - // IsValid returns true if the AutoVacuum is valid. func (a AutoVacuum) IsValid() bool { - return slices.Contains(validAutoVacuums, a) + switch a { + case AutoVacuumNone, AutoVacuumFull, AutoVacuumIncremental: + return true + default: + return false + } +} + +// String returns the string representation. +func (a AutoVacuum) String() string { + return string(a) } // Synchronous represents SQLite synchronous pragma values. @@ -202,14 +211,19 @@ const ( SynchronousExtra Synchronous = "EXTRA" ) -// validSynchronous lists the accepted Synchronous values. -var validSynchronous = []Synchronous{ - SynchronousOff, SynchronousNormal, SynchronousFull, SynchronousExtra, -} - // IsValid returns true if the Synchronous is valid. func (s Synchronous) IsValid() bool { - return slices.Contains(validSynchronous, s) + switch s { + case SynchronousOff, SynchronousNormal, SynchronousFull, SynchronousExtra: + return true + default: + return false + } +} + +// String returns the string representation. +func (s Synchronous) String() string { + return string(s) } // TxLock represents SQLite transaction lock mode. @@ -253,15 +267,19 @@ const ( TxLockExclusive TxLock = "exclusive" ) -// validTxLocks lists the accepted TxLock values; the empty string is valid -// and selects the driver default. -var validTxLocks = []TxLock{ - TxLockDeferred, TxLockImmediate, TxLockExclusive, "", -} - // IsValid returns true if the TxLock is valid. func (t TxLock) IsValid() bool { - return slices.Contains(validTxLocks, t) + switch t { + case TxLockDeferred, TxLockImmediate, TxLockExclusive, "": + return true + default: + return false + } +} + +// String returns the string representation. +func (t TxLock) String() string { + return string(t) } // Config holds SQLite database configuration with type-safe enums. @@ -349,6 +367,33 @@ func (c *Config) ToURL() (string, error) { return "", fmt.Errorf("invalid config: %w", err) } + var pragmas []string + + // Add pragma parameters only if they're set (non-zero/non-empty) + if c.BusyTimeout > 0 { + pragmas = append(pragmas, fmt.Sprintf("busy_timeout=%d", c.BusyTimeout)) + } + + if c.JournalMode != "" { + pragmas = append(pragmas, fmt.Sprintf("journal_mode=%s", c.JournalMode)) + } + + if c.AutoVacuum != "" { + pragmas = append(pragmas, fmt.Sprintf("auto_vacuum=%s", c.AutoVacuum)) + } + + if c.WALAutocheckpoint >= 0 { + pragmas = append(pragmas, fmt.Sprintf("wal_autocheckpoint=%d", c.WALAutocheckpoint)) + } + + if c.Synchronous != "" { + pragmas = append(pragmas, fmt.Sprintf("synchronous=%s", c.Synchronous)) + } + + if c.ForeignKeys { + pragmas = append(pragmas, "foreign_keys=ON") + } + // Handle different database types var baseURL string if c.Path == ":memory:" { @@ -358,36 +403,16 @@ func (c *Config) ToURL() (string, error) { } // Build query parameters - var queryParts []string + queryParts := make([]string, 0, 1+len(pragmas)) // Add _txlock first (it's a connection parameter, not a pragma) if c.TxLock != "" { queryParts = append(queryParts, "_txlock="+string(c.TxLock)) } - // Add pragma parameters only if they're set (non-zero/non-empty) - if c.BusyTimeout > 0 { - queryParts = append(queryParts, fmt.Sprintf("_pragma=busy_timeout=%d", c.BusyTimeout)) - } - - if c.JournalMode != "" { - queryParts = append(queryParts, fmt.Sprintf("_pragma=journal_mode=%s", c.JournalMode)) - } - - if c.AutoVacuum != "" { - queryParts = append(queryParts, fmt.Sprintf("_pragma=auto_vacuum=%s", c.AutoVacuum)) - } - - if c.WALAutocheckpoint >= 0 { - queryParts = append(queryParts, fmt.Sprintf("_pragma=wal_autocheckpoint=%d", c.WALAutocheckpoint)) - } - - if c.Synchronous != "" { - queryParts = append(queryParts, fmt.Sprintf("_pragma=synchronous=%s", c.Synchronous)) - } - - if c.ForeignKeys { - queryParts = append(queryParts, "_pragma=foreign_keys=ON") + // Add pragma parameters + for _, pragma := range pragmas { + queryParts = append(queryParts, "_pragma="+pragma) } if len(queryParts) > 0 { diff --git a/hscontrol/db/sqliteconfig/config_test.go b/hscontrol/db/sqliteconfig/config_test.go index 9f233902e..7829d9e92 100644 --- a/hscontrol/db/sqliteconfig/config_test.go +++ b/hscontrol/db/sqliteconfig/config_test.go @@ -98,6 +98,25 @@ func TestTxLock(t *testing.T) { } } +func TestTxLockString(t *testing.T) { + tests := []struct { + mode TxLock + want string + }{ + {TxLockDeferred, "deferred"}, + {TxLockImmediate, "immediate"}, + {TxLockExclusive, "exclusive"}, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := tt.mode.String(); got != tt.want { + t.Errorf("TxLock.String() = %q, want %q", got, tt.want) + } + }) + } +} + func TestConfigValidate(t *testing.T) { tests := []struct { name string diff --git a/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql b/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql deleted file mode 100644 index c45d0ff59..000000000 --- a/hscontrol/db/testdata/sqlite/null_tags_user_id_migration_test.sql +++ /dev/null @@ -1,85 +0,0 @@ --- Test SQL dump for the clear-tagged-node-user-id migration --- (202602201200-clear-tagged-node-user-id) against nodes whose tags --- column holds the JSON literal 'null'. --- --- A nil Strings slice marshals to the JSON literal `null`, so pre-0.29 --- databases contain untagged nodes with tags='null'. The migration's --- WHERE clause (tags IS NOT NULL AND tags != '[]' AND tags != '') treats --- the 4-character string 'null' as "tagged" and wrongly clears user_id, --- detaching the node from its owning user on upgrade. --- Fixes: https://github.com/juanfont/headscale/issues/3323 - -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; - --- Migrations table: every entry BEFORE clear-tagged-node-user-id has been --- applied. That migration is intentionally absent so it runs against this dump. -CREATE TABLE `migrations` (`id` text,PRIMARY KEY (`id`)); -INSERT INTO migrations VALUES('202312101416'); -INSERT INTO migrations VALUES('202312101430'); -INSERT INTO migrations VALUES('202402151347'); -INSERT INTO migrations VALUES('2024041121742'); -INSERT INTO migrations VALUES('202406021630'); -INSERT INTO migrations VALUES('202409271400'); -INSERT INTO migrations VALUES('202407191627'); -INSERT INTO migrations VALUES('202408181235'); -INSERT INTO migrations VALUES('202501221827'); -INSERT INTO migrations VALUES('202501311657'); -INSERT INTO migrations VALUES('202502070949'); -INSERT INTO migrations VALUES('202502131714'); -INSERT INTO migrations VALUES('202502171819'); -INSERT INTO migrations VALUES('202505091439'); -INSERT INTO migrations VALUES('202505141324'); -INSERT INTO migrations VALUES('202507021200'); -INSERT INTO migrations VALUES('202510311551'); -INSERT INTO migrations VALUES('202511101554-drop-old-idx'); -INSERT INTO migrations VALUES('202511011637-preauthkey-bcrypt'); -INSERT INTO migrations VALUES('202511122344-remove-newline-index'); -INSERT INTO migrations VALUES('202511131445-node-forced-tags-to-tags'); -INSERT INTO migrations VALUES('202601121700-migrate-hostinfo-request-tags'); - --- Users table -CREATE TABLE `users` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`display_name` text,`email` text,`provider_identifier` text,`provider` text,`profile_pic_url` text); -INSERT INTO users VALUES(1,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user1','User One','user1@example.com',NULL,NULL,NULL); -INSERT INTO users VALUES(2,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user2','User Two','user2@example.com',NULL,NULL,NULL); - --- Pre-auth keys table -CREATE TABLE `pre_auth_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`key` text,`user_id` integer,`reusable` numeric,`ephemeral` numeric DEFAULT false,`used` numeric DEFAULT false,`tags` text,`created_at` datetime,`expiration` datetime,`prefix` text,`hash` blob,CONSTRAINT `fk_pre_auth_keys_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL); - --- API keys table -CREATE TABLE `api_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`prefix` text,`hash` blob,`created_at` datetime,`expiration` datetime,`last_seen` datetime); - --- Nodes table - current schema (after the tags rename + last_seen/expiry reordering) -CREATE TABLE IF NOT EXISTS "nodes" (`id` integer PRIMARY KEY AUTOINCREMENT,`machine_key` text,`node_key` text,`disco_key` text,`endpoints` text,`host_info` text,`ipv4` text,`ipv6` text,`hostname` text,`given_name` varchar(63),`user_id` integer,`register_method` text,`tags` text,`auth_key_id` integer,`last_seen` datetime,`expiry` datetime,`approved_routes` text,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,CONSTRAINT `fk_nodes_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_nodes_auth_key` FOREIGN KEY (`auth_key_id`) REFERENCES `pre_auth_keys`(`id`)); - --- Node 1: tags='null' (untagged, nil slice marshalled to JSON null), owned by user2. --- After migration: user_id MUST be preserved (this is the bug). -INSERT INTO nodes VALUES(1,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e01','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605501','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57701','[]','{}','100.64.0.1','fd7a:115c:a1e0::1','node1','node1',2,'cli','null',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 2: genuinely tagged, owned by user1. --- After migration: user_id MUST be cleared to NULL (tagged nodes are owned by tags). -INSERT INTO nodes VALUES(2,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e02','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605502','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57702','[]','{}','100.64.0.2','fd7a:115c:a1e0::2','node2','node2',1,'cli','["tag:server"]',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 3: empty-array tags (untagged), owned by user1. --- After migration: user_id MUST be preserved. -INSERT INTO nodes VALUES(3,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e03','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605503','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57703','[]','{}','100.64.0.3','fd7a:115c:a1e0::3','node3','node3',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 4: empty-string tags (untagged), owned by user1. --- After migration: user_id MUST be preserved. -INSERT INTO nodes VALUES(4,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e04','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605504','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57704','[]','{}','100.64.0.4','fd7a:115c:a1e0::4','node4','node4',1,'cli','',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Policies table (empty) -CREATE TABLE `policies` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`data` text); - -DELETE FROM sqlite_sequence; -INSERT INTO sqlite_sequence VALUES('users',2); -INSERT INTO sqlite_sequence VALUES('nodes',4); -CREATE INDEX idx_users_deleted_at ON users(deleted_at); -CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); -CREATE INDEX idx_policies_deleted_at ON policies(deleted_at); -CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE provider_identifier IS NOT NULL; -CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier); -CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL; -CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''; - -COMMIT; diff --git a/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql b/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql deleted file mode 100644 index 163dcd352..000000000 --- a/hscontrol/db/testdata/sqlite/recover_null_tags_user_id_migration_test.sql +++ /dev/null @@ -1,87 +0,0 @@ --- Test SQL dump for the null-tags user_id RECOVERY migration. --- --- Represents a database that already upgraded to 0.29.0, where the buggy --- clear-tagged-node-user-id migration (202602201200) already cleared --- user_id on untagged nodes whose tags column held 'null'. The recovery --- migration runs against this state and re-derives user_id from the node's --- pre-auth key where possible. --- Fixes: https://github.com/juanfont/headscale/issues/3323 - -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; - --- Migrations table: everything through the current last migration has been --- applied (this DB already ran the buggy clear-tagged migration). The new --- recovery migration is intentionally absent so it runs against this dump. -CREATE TABLE `migrations` (`id` text,PRIMARY KEY (`id`)); -INSERT INTO migrations VALUES('202312101416'); -INSERT INTO migrations VALUES('202312101430'); -INSERT INTO migrations VALUES('202402151347'); -INSERT INTO migrations VALUES('2024041121742'); -INSERT INTO migrations VALUES('202406021630'); -INSERT INTO migrations VALUES('202409271400'); -INSERT INTO migrations VALUES('202407191627'); -INSERT INTO migrations VALUES('202408181235'); -INSERT INTO migrations VALUES('202501221827'); -INSERT INTO migrations VALUES('202501311657'); -INSERT INTO migrations VALUES('202502070949'); -INSERT INTO migrations VALUES('202502131714'); -INSERT INTO migrations VALUES('202502171819'); -INSERT INTO migrations VALUES('202505091439'); -INSERT INTO migrations VALUES('202505141324'); -INSERT INTO migrations VALUES('202507021200'); -INSERT INTO migrations VALUES('202510311551'); -INSERT INTO migrations VALUES('202511101554-drop-old-idx'); -INSERT INTO migrations VALUES('202511011637-preauthkey-bcrypt'); -INSERT INTO migrations VALUES('202511122344-remove-newline-index'); -INSERT INTO migrations VALUES('202511131445-node-forced-tags-to-tags'); -INSERT INTO migrations VALUES('202601121700-migrate-hostinfo-request-tags'); -INSERT INTO migrations VALUES('202602201200-clear-tagged-node-user-id'); -INSERT INTO migrations VALUES('202605221435-clear-zero-time-node-expiry'); - --- Users table -CREATE TABLE `users` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`display_name` text,`email` text,`provider_identifier` text,`provider` text,`profile_pic_url` text); -INSERT INTO users VALUES(1,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user1','User One','user1@example.com',NULL,NULL,NULL); -INSERT INTO users VALUES(2,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user2','User Two','user2@example.com',NULL,NULL,NULL); - --- Pre-auth keys table. Key 1 belongs to user2, key 2 to user1. -CREATE TABLE `pre_auth_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`key` text,`user_id` integer,`reusable` numeric,`ephemeral` numeric DEFAULT false,`used` numeric DEFAULT false,`tags` text,`created_at` datetime,`expiration` datetime,`prefix` text,`hash` blob,CONSTRAINT `fk_pre_auth_keys_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL); -INSERT INTO pre_auth_keys VALUES(1,NULL,2,1,false,true,NULL,'2024-01-01 00:00:00+00:00',NULL,'pak1',NULL); -INSERT INTO pre_auth_keys VALUES(2,NULL,1,1,false,true,NULL,'2024-01-01 00:00:00+00:00',NULL,'pak2',NULL); - --- API keys table -CREATE TABLE `api_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`prefix` text,`hash` blob,`created_at` datetime,`expiration` datetime,`last_seen` datetime); - --- Nodes table -CREATE TABLE IF NOT EXISTS "nodes" (`id` integer PRIMARY KEY AUTOINCREMENT,`machine_key` text,`node_key` text,`disco_key` text,`endpoints` text,`host_info` text,`ipv4` text,`ipv6` text,`hostname` text,`given_name` varchar(63),`user_id` integer,`register_method` text,`tags` text,`auth_key_id` integer,`last_seen` datetime,`expiry` datetime,`approved_routes` text,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,CONSTRAINT `fk_nodes_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_nodes_auth_key` FOREIGN KEY (`auth_key_id`) REFERENCES `pre_auth_keys`(`id`)); - --- Node 1: authkey-registered, tags='null', already orphaned (user_id NULL) by --- the buggy migration. auth_key_id=1 (user2). Recovery: user_id -> 2. -INSERT INTO nodes VALUES(1,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e01','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605501','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57701','[]','{}','100.64.0.1','fd7a:115c:a1e0::1','node1','node1',NULL,'authkey','null',1,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 2: genuinely tagged, user_id correctly cleared. Must stay NULL. -INSERT INTO nodes VALUES(2,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e02','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605502','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57702','[]','{}','100.64.0.2','fd7a:115c:a1e0::2','node2','node2',NULL,'authkey','["tag:server"]',2,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 3: CLI-registered, tags='null', orphaned, no auth_key_id. --- Unrecoverable: must stay NULL. -INSERT INTO nodes VALUES(3,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e03','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605503','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57703','[]','{}','100.64.0.3','fd7a:115c:a1e0::3','node3','node3',NULL,'cli','null',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 4: authkey-registered, untouched (user_id still set). Must stay user1. -INSERT INTO nodes VALUES(4,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e04','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605504','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57704','[]','{}','100.64.0.4','fd7a:115c:a1e0::4','node4','node4',1,'authkey','null',2,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Policies table (empty) -CREATE TABLE `policies` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`data` text); - -DELETE FROM sqlite_sequence; -INSERT INTO sqlite_sequence VALUES('users',2); -INSERT INTO sqlite_sequence VALUES('pre_auth_keys',2); -INSERT INTO sqlite_sequence VALUES('nodes',4); -CREATE INDEX idx_users_deleted_at ON users(deleted_at); -CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); -CREATE INDEX idx_policies_deleted_at ON policies(deleted_at); -CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE provider_identifier IS NOT NULL; -CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier); -CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL; -CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''; - -COMMIT; diff --git a/hscontrol/db/testdata/sqlite/zero_time_expiry_migration_test.sql b/hscontrol/db/testdata/sqlite/zero_time_expiry_migration_test.sql deleted file mode 100644 index 9a6d31856..000000000 --- a/hscontrol/db/testdata/sqlite/zero_time_expiry_migration_test.sql +++ /dev/null @@ -1,82 +0,0 @@ --- Test SQL dump for zero-time node expiry migration --- (202605221435-clear-zero-time-node-expiry) --- --- Pre-0.28 versions of Headscale persisted a zero time.Time as the string --- '0001-01-01 00:00:00+00:00' in nodes.expiry instead of NULL. Upgrading --- to 0.29 surfaces those rows as "expired" because they look like a --- timestamp at year 1. This dump exercises the data fix. --- Fixes: https://github.com/juanfont/headscale/issues/3284 - -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; - --- Migrations table: all entries BEFORE the zero-time fix have been applied. --- The new migration is intentionally absent so it runs against this dump. -CREATE TABLE `migrations` (`id` text,PRIMARY KEY (`id`)); -INSERT INTO migrations VALUES('202312101416'); -INSERT INTO migrations VALUES('202312101430'); -INSERT INTO migrations VALUES('202402151347'); -INSERT INTO migrations VALUES('2024041121742'); -INSERT INTO migrations VALUES('202406021630'); -INSERT INTO migrations VALUES('202409271400'); -INSERT INTO migrations VALUES('202407191627'); -INSERT INTO migrations VALUES('202408181235'); -INSERT INTO migrations VALUES('202501221827'); -INSERT INTO migrations VALUES('202501311657'); -INSERT INTO migrations VALUES('202502070949'); -INSERT INTO migrations VALUES('202502131714'); -INSERT INTO migrations VALUES('202502171819'); -INSERT INTO migrations VALUES('202505091439'); -INSERT INTO migrations VALUES('202505141324'); -INSERT INTO migrations VALUES('202507021200'); -INSERT INTO migrations VALUES('202510311551'); -INSERT INTO migrations VALUES('202511101554-drop-old-idx'); -INSERT INTO migrations VALUES('202511011637-preauthkey-bcrypt'); -INSERT INTO migrations VALUES('202511122344-remove-newline-index'); -INSERT INTO migrations VALUES('202511131445-node-forced-tags-to-tags'); -INSERT INTO migrations VALUES('202601121700-migrate-hostinfo-request-tags'); -INSERT INTO migrations VALUES('202602201200-clear-tagged-node-user-id'); - --- Users table -CREATE TABLE `users` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`display_name` text,`email` text,`provider_identifier` text,`provider` text,`profile_pic_url` text); -INSERT INTO users VALUES(1,'2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL,'user1','User One','user1@example.com',NULL,NULL,NULL); - --- Pre-auth keys table -CREATE TABLE `pre_auth_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`key` text,`user_id` integer,`reusable` numeric,`ephemeral` numeric DEFAULT false,`used` numeric DEFAULT false,`tags` text,`created_at` datetime,`expiration` datetime,`prefix` text,`hash` blob,CONSTRAINT `fk_pre_auth_keys_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL); - --- API keys table -CREATE TABLE `api_keys` (`id` integer PRIMARY KEY AUTOINCREMENT,`prefix` text,`hash` blob,`created_at` datetime,`expiration` datetime,`last_seen` datetime); - --- Nodes table - current schema (after the tags rename + last_seen/expiry reordering) -CREATE TABLE IF NOT EXISTS "nodes" (`id` integer PRIMARY KEY AUTOINCREMENT,`machine_key` text,`node_key` text,`disco_key` text,`endpoints` text,`host_info` text,`ipv4` text,`ipv6` text,`hostname` text,`given_name` varchar(63),`user_id` integer,`register_method` text,`tags` text,`auth_key_id` integer,`last_seen` datetime,`expiry` datetime,`approved_routes` text,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,CONSTRAINT `fk_nodes_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_nodes_auth_key` FOREIGN KEY (`auth_key_id`) REFERENCES `pre_auth_keys`(`id`)); - --- Node 1: zero-time expiry. After migration: expiry IS NULL. -INSERT INTO nodes VALUES(1,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e01','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605501','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57701','[]','{}','100.64.0.1','fd7a:115c:a1e0::1','node1','node1',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00','0001-01-01 00:00:00+00:00','[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 2: NULL expiry already. After migration: still NULL. -INSERT INTO nodes VALUES(2,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e02','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605502','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57702','[]','{}','100.64.0.2','fd7a:115c:a1e0::2','node2','node2',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00',NULL,'[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 3: real future expiry. After migration: preserved. -INSERT INTO nodes VALUES(3,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e03','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605503','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57703','[]','{}','100.64.0.3','fd7a:115c:a1e0::3','node3','node3',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00','2099-01-01 00:00:00+00:00','[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 4: real past expiry (legitimately expired). After migration: preserved. -INSERT INTO nodes VALUES(4,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e04','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605504','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57704','[]','{}','100.64.0.4','fd7a:115c:a1e0::4','node4','node4',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00','2020-01-01 00:00:00+00:00','[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Node 5: another zero-time row to confirm the WHERE clause matches multiple rows. -INSERT INTO nodes VALUES(5,'mkey:a0ab77456320823945ae0331823e3c0d516fae9585bd42698dfa1ac3d7679e05','nodekey:7c84167ab68f494942de14deb83587fd841843de2bac105b6c670048c1605505','discokey:53075b3c6cad3b62a2a29caea61beeb93f66b8c75cb89dac465236a5bbf57705','[]','{}','100.64.0.5','fd7a:115c:a1e0::5','node5','node5',1,'cli','[]',NULL,'2024-01-01 00:00:00+00:00','0001-01-01 00:00:00+00:00','[]','2024-01-01 00:00:00+00:00','2024-01-01 00:00:00+00:00',NULL); - --- Policies table (empty) -CREATE TABLE `policies` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`data` text); - -DELETE FROM sqlite_sequence; -INSERT INTO sqlite_sequence VALUES('users',1); -INSERT INTO sqlite_sequence VALUES('nodes',5); -CREATE INDEX idx_users_deleted_at ON users(deleted_at); -CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix); -CREATE INDEX idx_policies_deleted_at ON policies(deleted_at); -CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE provider_identifier IS NOT NULL; -CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier); -CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL; -CREATE UNIQUE INDEX IF NOT EXISTS idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != ''; - -COMMIT; diff --git a/hscontrol/db/text_serialiser.go b/hscontrol/db/text_serialiser.go index c315772bc..06a898d09 100644 --- a/hscontrol/db/text_serialiser.go +++ b/hscontrol/db/text_serialiser.go @@ -24,7 +24,7 @@ func isTextUnmarshaler(rv reflect.Value) bool { } func maybeInstantiatePtr(rv reflect.Value) { - if rv.Kind() == reflect.Pointer && rv.IsNil() { + if rv.Kind() == reflect.Ptr && rv.IsNil() { np := reflect.New(rv.Type().Elem()) rv.Set(np) } @@ -34,8 +34,8 @@ func decodingError(name string, err error) error { return fmt.Errorf("decoding to %s: %w", name, err) } -// TextSerialiser implements the [schema.SerializerInterface] for fields that -// have a type that implements [encoding.TextUnmarshaler]. +// TextSerialiser implements the Serialiser interface for fields that +// have a type that implements encoding.TextUnmarshaler. type TextSerialiser struct{} func (TextSerialiser) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue any) error { @@ -43,49 +43,49 @@ func (TextSerialiser) Scan(ctx context.Context, field *schema.Field, dst reflect // If the field is a pointer, we need to dereference it to get the actual type // so we do not end with a second pointer. - if fieldValue.Elem().Kind() == reflect.Pointer { + if fieldValue.Elem().Kind() == reflect.Ptr { fieldValue = fieldValue.Elem() } - if dbValue == nil { - return nil - } + if dbValue != nil { + var bytes []byte - var bytes []byte - - switch v := dbValue.(type) { - case []byte: - bytes = v - case string: - bytes = []byte(v) - default: - return fmt.Errorf("%w: %#v", errUnmarshalTextValue, dbValue) - } - - if !isTextUnmarshaler(fieldValue) { - return fmt.Errorf("%w: %T", errUnsupportedType, fieldValue.Interface()) - } - - maybeInstantiatePtr(fieldValue) - f := fieldValue.MethodByName("UnmarshalText") - args := []reflect.Value{reflect.ValueOf(bytes)} - - ret := f.Call(args) - if !ret[0].IsNil() { - if err, ok := ret[0].Interface().(error); ok { - return decodingError(field.Name, err) + switch v := dbValue.(type) { + case []byte: + bytes = v + case string: + bytes = []byte(v) + default: + return fmt.Errorf("%w: %#v", errUnmarshalTextValue, dbValue) } - } - // If the underlying field is to a pointer type, we need to - // assign the value as a pointer to it. - // If it is not a pointer, we need to assign the value to the - // field. - dstField := field.ReflectValueOf(ctx, dst) - if dstField.Kind() == reflect.Pointer { - dstField.Set(fieldValue) - } else { - dstField.Set(fieldValue.Elem()) + if isTextUnmarshaler(fieldValue) { + maybeInstantiatePtr(fieldValue) + f := fieldValue.MethodByName("UnmarshalText") + args := []reflect.Value{reflect.ValueOf(bytes)} + + ret := f.Call(args) + if !ret[0].IsNil() { + if err, ok := ret[0].Interface().(error); ok { + return decodingError(field.Name, err) + } + } + + // If the underlying field is to a pointer type, we need to + // assign the value as a pointer to it. + // If it is not a pointer, we need to assign the value to the + // field. + dstField := field.ReflectValueOf(ctx, dst) + if dstField.Kind() == reflect.Ptr { + dstField.Set(fieldValue) + } else { + dstField.Set(fieldValue.Elem()) + } + + return nil + } else { + return fmt.Errorf("%w: %T", errUnsupportedType, fieldValue.Interface()) + } } return nil @@ -97,7 +97,7 @@ func (TextSerialiser) Value(ctx context.Context, field *schema.Field, dst reflec // If the value is nil, we return nil, however, go nil values are not // always comparable, particularly when reflection is involved: // https://dev.to/arxeiss/in-go-nil-is-not-equal-to-nil-sometimes-jn8 - if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) { + if v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) { return nil, nil //nolint:nilnil // intentional: nil value for GORM serializer } diff --git a/hscontrol/db/users.go b/hscontrol/db/users.go index 86ee936fe..b8bb01afb 100644 --- a/hscontrol/db/users.go +++ b/hscontrol/db/users.go @@ -12,10 +12,11 @@ import ( ) var ( - ErrUserExists = errors.New("user already exists") - ErrUserNotFound = errors.New("user not found") - ErrUserStillHasNodes = errors.New("user not empty: node(s) found") - ErrUserNotUnique = errors.New("expected exactly one user") + ErrUserExists = errors.New("user already exists") + ErrUserNotFound = errors.New("user not found") + ErrUserStillHasNodes = errors.New("user not empty: node(s) found") + ErrUserWhereInvalidCount = errors.New("expect 0 or 1 where User structs") + ErrUserNotUnique = errors.New("expected exactly one user") ) func (hsdb *HSDatabase) CreateUser(user types.User) (*types.User, error) { @@ -24,7 +25,7 @@ func (hsdb *HSDatabase) CreateUser(user types.User) (*types.User, error) { }) } -// CreateUser creates a new [types.User]. Returns error if could not be created +// CreateUser creates a new User. Returns error if could not be created // or another user already exists. func CreateUser(tx *gorm.DB, user types.User) (*types.User, error) { err := util.ValidateUsername(user.Name) @@ -46,7 +47,7 @@ func (hsdb *HSDatabase) DestroyUser(uid types.UserID) error { }) } -// DestroyUser destroys a [types.User]. Returns error if the [types.User] does +// DestroyUser destroys a User. Returns error if the User does // not exist or if there are user-owned nodes associated with it. // Tagged nodes have user_id = NULL so they do not block deletion. func DestroyUser(tx *gorm.DB, uid types.UserID) error { @@ -91,9 +92,11 @@ func (hsdb *HSDatabase) RenameUser(uid types.UserID, newName string) error { var ErrCannotChangeOIDCUser = errors.New("cannot edit OIDC user") -// RenameUser renames a [types.User]. Returns error if the [types.User] does -// not exist or if another [types.User] exists with the new name. +// RenameUser renames a User. Returns error if the User does +// not exist or if another User exists with the new name. func RenameUser(tx *gorm.DB, uid types.UserID, newName string) error { + var err error + oldUser, err := GetUserByID(tx, uid) if err != nil { return err @@ -151,15 +154,24 @@ func GetUserByOIDCIdentifier(tx *gorm.DB, id string) (*types.User, error) { return &user, nil } -func (hsdb *HSDatabase) ListUsers(filter *types.User) ([]types.User, error) { - return ListUsers(hsdb.DB, filter) +func (hsdb *HSDatabase) ListUsers(where ...*types.User) ([]types.User, error) { + return ListUsers(hsdb.DB, where...) } -// ListUsers gets all the existing users, optionally filtered by a non-nil filter. -func ListUsers(tx *gorm.DB, filter *types.User) ([]types.User, error) { +// ListUsers gets all the existing users. +func ListUsers(tx *gorm.DB, where ...*types.User) ([]types.User, error) { + if len(where) > 1 { + return nil, fmt.Errorf("%w, got %d", ErrUserWhereInvalidCount, len(where)) + } + + var user *types.User + if len(where) == 1 { + user = where[0] + } + users := []types.User{} - err := tx.Where(filter).Find(&users).Error + err := tx.Where(user).Find(&users).Error if err != nil { return nil, err } @@ -192,7 +204,7 @@ func ListNodesByUser(tx *gorm.DB, uid types.UserID) (types.Nodes, error) { uidPtr := uint(uid) - err := preloadNode(tx).Where(&types.Node{UserID: &uidPtr}).Find(&nodes).Error + err := tx.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&types.Node{UserID: &uidPtr}).Find(&nodes).Error if err != nil { return nil, err } @@ -205,7 +217,10 @@ func (hsdb *HSDatabase) CreateUserForTest(name ...string) *types.User { panic("CreateUserForTest can only be called during tests") } - userName := firstOr("testuser", name) + userName := "testuser" + if len(name) > 0 && name[0] != "" { + userName = name[0] + } user, err := hsdb.CreateUser(types.User{Name: userName}) if err != nil { @@ -220,7 +235,10 @@ func (hsdb *HSDatabase) CreateUsersForTest(count int, namePrefix ...string) []*t panic("CreateUsersForTest can only be called during tests") } - prefix := firstOr("testuser", namePrefix) + prefix := "testuser" + if len(namePrefix) > 0 && namePrefix[0] != "" { + prefix = namePrefix[0] + } users := make([]*types.User, count) for i := range count { diff --git a/hscontrol/db/users_test.go b/hscontrol/db/users_test.go index 2a755ff3c..cbb1c3cee 100644 --- a/hscontrol/db/users_test.go +++ b/hscontrol/db/users_test.go @@ -17,7 +17,7 @@ func TestCreateAndDestroyUser(t *testing.T) { user := db.CreateUserForTest("test") assert.Equal(t, "test", user.Name) - users, err := db.ListUsers(nil) + users, err := db.ListUsers() require.NoError(t, err) assert.Len(t, users, 1) @@ -123,7 +123,7 @@ func TestDestroyUserErrors(t *testing.T) { result := db.DB.First(&survivingNode, "id = ?", node.ID) require.NoError(t, result.Error) assert.Nil(t, survivingNode.UserID) - assert.Equal(t, []string{"tag:server"}, survivingNode.Tags.List()) + assert.Equal(t, []string{"tag:server"}, survivingNode.Tags) }, }, { @@ -227,7 +227,7 @@ func TestRenameUser(t *testing.T) { userTest := db.CreateUserForTest("test") assert.Equal(t, "test", userTest.Name) - users, err := db.ListUsers(nil) + users, err := db.ListUsers() require.NoError(t, err) assert.Len(t, users, 1) diff --git a/hscontrol/db/versioncheck.go b/hscontrol/db/versioncheck.go index 199b5964d..d78b00d31 100644 --- a/hscontrol/db/versioncheck.go +++ b/hscontrol/db/versioncheck.go @@ -3,7 +3,6 @@ package db import ( "errors" "fmt" - "regexp" "strconv" "strings" "time" @@ -11,7 +10,6 @@ import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/rs/zerolog/log" "gorm.io/gorm" - "gorm.io/gorm/clause" ) var errVersionUpgrade = errors.New("version upgrade not supported") @@ -67,24 +65,26 @@ func parseVersion(s string) (semver, error) { return semver{}, fmt.Errorf("%q: %w", s, errVersionFormat) } - var out [3]int - - names := [...]string{"major", "minor", "patch"} - - for i, p := range parts { - n, err := strconv.Atoi(p) - if err != nil { - return semver{}, fmt.Errorf("invalid %s version in %q: %w", names[i], s, err) - } - - out[i] = n + major, err := strconv.Atoi(parts[0]) + if err != nil { + return semver{}, fmt.Errorf("invalid major version in %q: %w", s, err) } - return semver{Major: out[0], Minor: out[1], Patch: out[2]}, nil + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return semver{}, fmt.Errorf("invalid minor version in %q: %w", s, err) + } + + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return semver{}, fmt.Errorf("invalid patch version in %q: %w", s, err) + } + + return semver{Major: major, Minor: minor, Patch: patch}, nil } // ensureDatabaseVersionTable creates the database_versions table if it -// does not already exist. Uses [gorm.DB.AutoMigrate] to handle dialect +// does not already exist. Uses GORM AutoMigrate to handle dialect // differences between SQLite (datetime) and PostgreSQL (timestamp). // This runs before gormigrate migrations. func ensureDatabaseVersionTable(db *gorm.DB) error { @@ -117,60 +117,32 @@ func getDatabaseVersion(db *gorm.DB) (string, error) { func setDatabaseVersion(db *gorm.DB, version string) error { now := time.Now().UTC() - err := db.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "id"}}, - DoUpdates: clause.AssignmentColumns([]string{"version", "updated_at"}), - }).Create(&DatabaseVersion{ID: 1, Version: version, UpdatedAt: now}).Error - if err != nil { - return fmt.Errorf("upserting database version: %w", err) + // Try update first, then insert if no rows affected. + result := db.Exec( + "UPDATE database_versions SET version = ?, updated_at = ? WHERE id = 1", + version, now, + ) + if result.Error != nil { + return fmt.Errorf("updating database version: %w", result.Error) + } + + if result.RowsAffected == 0 { + err := db.Exec( + "INSERT INTO database_versions (id, version, updated_at) VALUES (1, ?, ?)", + version, now, + ).Error + if err != nil { + return fmt.Errorf("inserting database version: %w", err) + } } return nil } -// pseudoVersionTimeLayout is Go's pseudo-version timestamp layout -// (golang.org/ref/mod#pseudo-versions): UTC yyyymmddhhmmss. -const pseudoVersionTimeLayout = "20060102150405" - -// pseudoVersionSuffix matches the trailing "<14 digits>-<12 -// lowercase hex>" of a Go module pseudo-version. The base form -// (vX.0.0--) uses "-" before the timestamp; the -// pre-release-ancestor and release-ancestor forms -// (vX.Y.Z-pre.0.- and vX.Y.(Z+1)-0.-) use "." -// because the digit-only "0" marker precedes the timestamp. -var pseudoVersionSuffix = regexp.MustCompile(`[-.](\d{14})-[0-9a-f]{12}$`) - -// pseudoVersionTime returns the embedded commit time when v is a -// syntactically and semantically valid Go module pseudo-version. The -// timestamp must parse as a real UTC time; lookalikes with malformed -// dates (e.g. month 13, day 30 in February) are rejected. -func pseudoVersionTime(v string) (time.Time, bool) { - m := pseudoVersionSuffix.FindStringSubmatch(v) - if m == nil { - return time.Time{}, false - } - - t, err := time.Parse(pseudoVersionTimeLayout, m[1]) - if err != nil { - return time.Time{}, false - } - - return t, true -} - // isDev reports whether a version string represents a development build -// that should skip version checking. Go module pseudo-versions (used by -// untagged main-sha builds, where runtime/debug.BuildInfo falls back to -// vX.Y.Z--) are treated as dev to avoid poisoning -// database_versions with synthetic baselines. +// that should skip version checking. func isDev(version string) bool { - if version == "" || version == "dev" || version == "(devel)" { - return true - } - - _, ok := pseudoVersionTime(version) - - return ok + return version == "" || version == "dev" || version == "(devel)" } // checkVersionUpgradePath verifies that the running headscale version diff --git a/hscontrol/db/versioncheck_test.go b/hscontrol/db/versioncheck_test.go index 87089ca51..83c9ab5cf 100644 --- a/hscontrol/db/versioncheck_test.go +++ b/hscontrol/db/versioncheck_test.go @@ -3,7 +3,6 @@ package db import ( "fmt" "testing" - "time" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" @@ -57,206 +56,12 @@ func TestSemverString(t *testing.T) { assert.Equal(t, "v0.28.3", s.String()) } -func TestPseudoVersionTime(t *testing.T) { - parseTS := func(s string) time.Time { - t.Helper() - - ts, err := time.Parse(pseudoVersionTimeLayout, s) - require.NoError(t, err) - - return ts - } - - tests := []struct { - name string - input string - wantOK bool - wantTime time.Time - }{ - // Accept: all three Go pseudo-version shapes. - { - name: "no ancestor tag (v0.0.0 base)", - input: "v0.0.0-20260522092201-58a85b68b3d9", - wantOK: true, - wantTime: parseTS("20260522092201"), - }, - { - name: "ancestor is pre-release tag", - input: "v0.29.0-beta.1.0.20260522092201-58a85b68b3d9", - wantOK: true, - wantTime: parseTS("20260522092201"), - }, - { - name: "ancestor is release tag", - input: "v0.29.1-0.20260522092201-58a85b68b3d9", - wantOK: true, - wantTime: parseTS("20260522092201"), - }, - { - name: "earliest realistic Go module date", - input: "v0.0.0-20180101000000-000000000000", - wantOK: true, - wantTime: parseTS("20180101000000"), - }, - - // Reject: real release tags must not look like pseudo-versions. - {name: "release tag", input: "v0.29.0"}, - {name: "pre-release tag", input: "v0.29.0-beta.1"}, - {name: "rc tag", input: "v0.29.0-rc1"}, - {name: "tag with build metadata", input: "v0.29.0+build123"}, - - // Reject: literals handled elsewhere. - {name: "empty", input: ""}, - {name: "dev literal", input: "dev"}, - {name: "devel literal", input: "(devel)"}, - - // Reject: malformed hash. - {name: "hash too short", input: "v0.0.0-20260522092201-58a85b6"}, - {name: "hash too long", input: "v0.0.0-20260522092201-58a85b68b3d9aa"}, - {name: "hash uppercase hex", input: "v0.0.0-20260522092201-58A85B68B3D9"}, - {name: "hash non-hex", input: "v0.0.0-20260522092201-zzzzzzzzzzzz"}, - - // Reject: malformed timestamp. - {name: "timestamp too short", input: "v0.0.0-2026052209220-58a85b68b3d9"}, - {name: "timestamp too long", input: "v0.0.0-202605220922010-58a85b68b3d9"}, - {name: "invalid month", input: "v0.0.0-20261322092201-58a85b68b3d9"}, - {name: "invalid day", input: "v0.0.0-20260230092201-58a85b68b3d9"}, - {name: "invalid hour", input: "v0.0.0-20260522252201-58a85b68b3d9"}, - {name: "invalid minute", input: "v0.0.0-20260522096001-58a85b68b3d9"}, - {name: "invalid second", input: "v0.0.0-20260522092260-58a85b68b3d9"}, - {name: "leap day on non-leap year", input: "v0.0.0-20230229000000-58a85b68b3d9"}, - - // Reject: missing components. - {name: "missing date and hash", input: "v0.0.0-"}, - {name: "missing hash", input: "v0.0.0-20260522092201-"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, ok := pseudoVersionTime(tt.input) - assert.Equal(t, tt.wantOK, ok) - - if tt.wantOK { - assert.True(t, got.Equal(tt.wantTime), - "want %s, got %s", tt.wantTime, got) - } - }) - } -} - func TestIsDev(t *testing.T) { - tests := []struct { - name string - input string - want bool - }{ - // Existing literals. - {name: "empty", input: "", want: true}, - {name: "dev", input: "dev", want: true}, - {name: "devel", input: "(devel)", want: true}, - {name: "release tag", input: "v0.28.0", want: false}, - {name: "release tag no v", input: "0.28.0", want: false}, - {name: "pre-release tag", input: "v0.29.0-beta.1", want: false}, - - // Go module pseudo-versions — all three shapes Go emits per - // golang.org/ref/mod#pseudo-versions. Untagged commits - // (such as main-sha docker builds) must be treated as dev - // so they neither poison database_versions nor trip the - // upgrade-path guard. - { - name: "pseudo v0.0.0 base", - input: "v0.0.0-20260522092201-58a85b68b3d9", - want: true, - }, - { - name: "pseudo from pre-release ancestor", - input: "v0.29.0-beta.1.0.20260522092201-58a85b68b3d9", - want: true, - }, - { - name: "pseudo from release ancestor", - input: "v0.29.1-0.20260522092201-58a85b68b3d9", - want: true, - }, - - // Malformed pseudo-version lookalikes must NOT be treated - // as dev — they fall through to the semver parser. - { - name: "malformed timestamp not dev", - input: "v0.0.0-20261322092201-58a85b68b3d9", - want: false, - }, - { - name: "hash wrong length not dev", - input: "v0.0.0-20260522092201-58a85b6", - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, isDev(tt.input)) - }) - } -} - -// TestCheckVersionUpgradePath_StoredPseudoVersion exercises the -// upgrade path when database_versions holds a Go module pseudo-version -// written by an untagged main-sha build. Without dev handling, the -// stored pseudo-version parses as v0.0.0 and the next real release -// trips the multi-minor guard. -func TestCheckVersionUpgradePath_StoredPseudoVersion(t *testing.T) { - tests := []struct { - name string - stored string - currentVersion string - }{ - { - name: "v0.0.0 base pseudo to real release", - stored: "v0.0.0-20260520093041-e4e742c776ee", - currentVersion: "v0.29.0-beta.1", - }, - { - name: "pseudo from pre-release ancestor", - stored: "v0.29.0-beta.1.0.20260520093041-e4e742c776ee", - currentVersion: "v0.29.0", - }, - { - name: "pseudo from release ancestor", - stored: "v0.28.1-0.20260520093041-e4e742c776ee", - currentVersion: "v0.29.0", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - db := versionTestDB(t) - require.NoError(t, setDatabaseVersion(db, tt.stored)) - err := checkVersionUpgradePathFromVersions(db, tt.currentVersion) - assert.NoError(t, err) - }) - } -} - -// TestCheckVersionUpgradePath_CurrentPseudoDoesNotPoison locks the -// contract that a main-sha (pseudo-version) binary must preserve the -// stored real release so the next real release can upgrade cleanly. -// Mirrors the gating in db.go around setDatabaseVersion. -func TestCheckVersionUpgradePath_CurrentPseudoDoesNotPoison(t *testing.T) { - db := versionTestDB(t) - require.NoError(t, setDatabaseVersion(db, "v0.28.0")) - - current := "v0.0.0-20260522092201-58a85b68b3d9" - err := checkVersionUpgradePathFromVersions(db, current) - require.NoError(t, err) - - // Mirror db.go: only write the current version when !isDev. - if !isDev(current) { - require.NoError(t, setDatabaseVersion(db, current)) - } - - stored, err := getDatabaseVersion(db) - require.NoError(t, err) - assert.Equal(t, "v0.28.0", stored, - "pseudo-version run must not overwrite stored release") + assert.True(t, isDev("")) + assert.True(t, isDev("dev")) + assert.True(t, isDev("(devel)")) + assert.False(t, isDev("v0.28.0")) + assert.False(t, isDev("0.28.0")) } // versionTestDB creates an in-memory SQLite database with the diff --git a/hscontrol/debug.go b/hscontrol/debug.go index cc2a360bf..73eeb154c 100644 --- a/hscontrol/debug.go +++ b/hscontrol/debug.go @@ -1,14 +1,12 @@ package hscontrol import ( - "cmp" "context" "encoding/json" "fmt" "net" "net/http" "net/netip" - "slices" "strings" "time" @@ -21,9 +19,9 @@ import ( "tailscale.com/tsweb" ) -// protectedDebugHandler wraps an [http.Handler] with an access check that +// protectedDebugHandler wraps an http.Handler with an access check that // allows requests from loopback, Tailscale CGNAT IPs, and private -// (RFC 1918 / RFC 4193) addresses. This extends [tsweb.Protected] which +// (RFC 1918 / RFC 4193) addresses. This extends tsweb.Protected which // only allows loopback and Tailscale IPs. func protectedDebugHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -33,7 +31,7 @@ func protectedDebugHandler(h http.Handler) http.Handler { return } - // [tsweb.AllowDebugAccess] rejects X-Forwarded-For and non-TS IPs. + // tsweb.AllowDebugAccess rejects X-Forwarded-For and non-TS IPs. // Additionally allow private/LAN addresses so operators can reach // debug endpoints from their local network without tailscaled. ipStr, _, err := net.SplitHostPort(r.RemoteAddr) @@ -50,45 +48,51 @@ func protectedDebugHandler(h http.Handler) http.Handler { }) } -// writeJSON marshals v with indentation and writes it as a 200 JSON response. -func writeJSON(w http.ResponseWriter, v any) { - b, err := json.MarshalIndent(v, "", " ") - if err != nil { - httpError(w, err) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(b) -} - -// writeDebug renders a debug endpoint as JSON or text/plain depending on the -// request's Accept header. JSON is produced only when explicitly requested; -// text/plain is the default for backward compatibility. -func writeDebug(w http.ResponseWriter, r *http.Request, jsonVal func() any, textVal func() string) { - if strings.Contains(r.Header.Get("Accept"), "application/json") { - writeJSON(w, jsonVal()) - return - } - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(textVal())) -} - func (h *Headscale) debugHTTPServer() *http.Server { debugMux := http.NewServeMux() debug := tsweb.Debugger(debugMux) // State overview endpoint debug.Handle("overview", "State overview", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.state.DebugOverviewJSON() }, h.state.DebugOverview) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + overview := h.state.DebugOverviewJSON() + + overviewJSON, err := json.MarshalIndent(overview, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(overviewJSON) + } else { + // Default to text/plain for backward compatibility + overview := h.state.DebugOverview() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(overview)) + } })) // Configuration endpoint debug.Handle("config", "Current configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, h.state.DebugConfig()) + config := h.state.DebugConfig() + + configJSON, err := json.MarshalIndent(config, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(configJSON) })) // Policy endpoint @@ -119,37 +123,157 @@ func (h *Headscale) debugHTTPServer() *http.Server { return } - writeJSON(w, filter) + filterJSON, err := json.MarshalIndent(filter, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(filterJSON) })) // SSH policies endpoint debug.Handle("ssh", "SSH policies per node", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, h.state.DebugSSHPolicies()) + sshPolicies := h.state.DebugSSHPolicies() + + sshJSON, err := json.MarshalIndent(sshPolicies, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(sshJSON) })) // DERP map endpoint debug.Handle("derp", "DERP map configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.state.DebugDERPJSON() }, h.state.DebugDERPMap) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + derpInfo := h.state.DebugDERPJSON() + + derpJSON, err := json.MarshalIndent(derpInfo, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(derpJSON) + } else { + // Default to text/plain for backward compatibility + derpInfo := h.state.DebugDERPMap() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(derpInfo)) + } })) - // [state.NodeStore] endpoint + // NodeStore endpoint debug.Handle("nodestore", "NodeStore information", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.state.DebugNodeStoreJSON() }, h.state.DebugNodeStore) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + nodeStoreNodes := h.state.DebugNodeStoreJSON() + + nodeStoreJSON, err := json.MarshalIndent(nodeStoreNodes, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(nodeStoreJSON) + } else { + // Default to text/plain for backward compatibility + nodeStoreInfo := h.state.DebugNodeStore() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(nodeStoreInfo)) + } })) // Registration cache endpoint debug.Handle("registration-cache", "Registration cache information", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, h.state.DebugRegistrationCache()) + cacheInfo := h.state.DebugRegistrationCache() + + cacheJSON, err := json.MarshalIndent(cacheInfo, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(cacheJSON) })) // Routes endpoint debug.Handle("routes", "Primary routes", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.state.DebugRoutes() }, h.state.DebugRoutesString) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + routes := h.state.DebugRoutes() + + routesJSON, err := json.MarshalIndent(routes, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(routesJSON) + } else { + // Default to text/plain for backward compatibility + routes := h.state.DebugRoutesString() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(routes)) + } })) // Policy manager endpoint debug.Handle("policy-manager", "Policy manager state", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.state.DebugPolicyManagerJSON() }, h.state.DebugPolicyManager) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + policyManagerInfo := h.state.DebugPolicyManagerJSON() + + policyManagerJSON, err := json.MarshalIndent(policyManagerInfo, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(policyManagerJSON) + } else { + // Default to text/plain for backward compatibility + policyManagerInfo := h.state.DebugPolicyManager() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(policyManagerInfo)) + } })) debug.Handle("mapresponses", "Map responses for all nodes", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -166,15 +290,46 @@ func (h *Headscale) debugHTTPServer() *http.Server { return } - writeJSON(w, res) + resJSON, err := json.MarshalIndent(res, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resJSON) })) - // [mapper.Batcher] endpoint + // Batcher endpoint debug.Handle("batcher", "Batcher connected nodes", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeDebug(w, r, func() any { return h.debugBatcherJSON() }, h.debugBatcher) + // Check Accept header to determine response format + acceptHeader := r.Header.Get("Accept") + wantsJSON := strings.Contains(acceptHeader, "application/json") + + if wantsJSON { + batcherInfo := h.debugBatcherJSON() + + batcherJSON, err := json.MarshalIndent(batcherInfo, "", " ") + if err != nil { + httpError(w, err) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(batcherJSON) + } else { + // Default to text/plain for backward compatibility + batcherInfo := h.debugBatcher() + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(batcherInfo)) + } })) - // Ping endpoint: sends a [tailcfg.PingRequest] to a node and waits for it to respond. + // Ping endpoint: sends a PingRequest to a node and waits for it to respond. // Supports POST (form submit) and GET with ?node= (clickable quick-ping links). debug.Handle("ping", "Ping a node to check connectivity", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( @@ -206,12 +361,12 @@ func (h *Headscale) debugHTTPServer() *http.Server { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(templates.PingPage(query, result, nodes).Render())) //nolint:gosec // G705: templ component auto-escapes + _, _ = w.Write([]byte(templates.PingPage(query, result, nodes).Render())) })) - // [statsviz.Register] would mount handlers directly on the raw mux, + // statsviz.Register would mount handlers directly on the raw mux, // bypassing the access gate. Build the server by hand and wrap - // each handler with [protectedDebugHandler]. + // each handler with protectedDebugHandler. statsvizSrv, err := statsviz.NewServer() if err == nil { debugMux.Handle("/debug/statsviz/", protectedDebugHandler(statsvizSrv.Index())) @@ -247,9 +402,9 @@ func (h *Headscale) debugBatcher() string { activeConnections int } - debugInfo := h.mapBatcher.Debug() - nodes := make([]nodeStatus, 0, len(debugInfo)) + var nodes []nodeStatus + debugInfo := h.mapBatcher.Debug() for nodeID, info := range debugInfo { nodes = append(nodes, nodeStatus{ id: nodeID, @@ -264,9 +419,13 @@ func (h *Headscale) debugBatcher() string { } // Sort by node ID - slices.SortFunc(nodes, func(a, b nodeStatus) int { - return cmp.Compare(a.id, b.id) - }) + for i := 0; i < len(nodes); i++ { + for j := i + 1; j < len(nodes); j++ { + if nodes[i].id > nodes[j].id { + nodes[i], nodes[j] = nodes[j], nodes[i] + } + } + } // Output sorted nodes for _, node := range nodes { @@ -351,7 +510,7 @@ func (h *Headscale) connectedNodesList() []templates.ConnectedNode { const pingTimeout = 30 * time.Second -// doPing sends a [tailcfg.PingRequest] to the node identified by query and waits for a response. +// doPing sends a PingRequest to the node identified by query and waits for a response. func (h *Headscale) doPing(ctx context.Context, query string) *templates.PingResult { if query == "" { return &templates.PingResult{ diff --git a/hscontrol/derp/derp.go b/hscontrol/derp/derp.go index beeb66ada..3dc06d07d 100644 --- a/hscontrol/derp/derp.go +++ b/hscontrol/derp/derp.go @@ -6,6 +6,7 @@ import ( "encoding/json" "hash/crc64" "io" + "maps" "math/rand" "net/http" "net/url" @@ -22,13 +23,19 @@ import ( ) func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) { - b, err := os.ReadFile(path) + derpFile, err := os.Open(path) if err != nil { return nil, err } + defer derpFile.Close() var derpMap tailcfg.DERPMap + b, err := io.ReadAll(derpFile) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(b, &derpMap) return &derpMap, err @@ -66,11 +73,11 @@ func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) { return &derpMap, err } -// mergeDERPMaps naively merges a list of [tailcfg.DERPMap] values into a single -// [tailcfg.DERPMap], it will _only_ look at the Regions, an integer. -// If a region exists in two of the given [tailcfg.DERPMap] values, the region -// form the _last_ [tailcfg.DERPMap] will be preserved. -// An empty [tailcfg.DERPMap] list will result in a [tailcfg.DERPMap] with no regions. +// mergeDERPMaps naively merges a list of DERPMaps into a single +// DERPMap, it will _only_ look at the Regions, an integer. +// If a region exists in two of the given DERPMaps, the region +// form the _last_ DERPMap will be preserved. +// An empty DERPMap list will result in a DERPMap with no regions. func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap { result := tailcfg.DERPMap{ OmitDefaultRegions: false, @@ -78,13 +85,12 @@ func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap { } for _, derpMap := range derpMaps { - // Clone each region: copying the pointer would let a later in-place - // shuffle alias regions shared with the source map or a previously - // served map, racing concurrent readers. - for id, region := range derpMap.Regions { - if cloned := region.Clone(); cloned != nil { - result.Regions[id] = cloned - } + maps.Copy(result.Regions, derpMap.Regions) + } + + for id, region := range result.Regions { + if region == nil { + delete(result.Regions, id) } } @@ -142,13 +148,14 @@ func shuffleDERPMap(dm *tailcfg.DERPMap) { continue } - derpRandom().Shuffle(len(region.Nodes), reflect.Swapper(region.Nodes)) + dm.Regions[id] = shuffleRegionNoClone(region) } } var crc64Table = crc64.MakeTable(crc64.ISO) var ( + derpRandomOnce sync.Once derpRandomInst *rand.Rand derpRandomMu sync.Mutex ) @@ -157,10 +164,12 @@ func derpRandom() *rand.Rand { derpRandomMu.Lock() defer derpRandomMu.Unlock() - if derpRandomInst == nil { + derpRandomOnce.Do(func() { seed := cmp.Or(viper.GetString("dns.base_domain"), time.Now().String()) - derpRandomInst = rand.New(rand.NewSource(int64(crc64.Checksum([]byte(seed), crc64Table)))) //nolint:gosec // weak random is fine for DERP scrambling - } + rnd := rand.New(rand.NewSource(0)) //nolint:gosec // weak random is fine for DERP scrambling + rnd.Seed(int64(crc64.Checksum([]byte(seed), crc64Table))) //nolint:gosec // safe conversion + derpRandomInst = rnd + }) return derpRandomInst } @@ -169,5 +178,11 @@ func resetDerpRandomForTesting() { derpRandomMu.Lock() defer derpRandomMu.Unlock() + derpRandomOnce = sync.Once{} derpRandomInst = nil } + +func shuffleRegionNoClone(r *tailcfg.DERPRegion) *tailcfg.DERPRegion { + derpRandom().Shuffle(len(r.Nodes), reflect.Swapper(r.Nodes)) + return r +} diff --git a/hscontrol/derp/derp_merge_test.go b/hscontrol/derp/derp_merge_test.go deleted file mode 100644 index e1ac41200..000000000 --- a/hscontrol/derp/derp_merge_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package derp - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "tailscale.com/tailcfg" -) - -// TestMergeDERPMapsClonesRegions ensures merged DERP maps own their regions -// rather than aliasing the source pointers, so a later in-place node shuffle -// cannot mutate a shared or previously served map. -func TestMergeDERPMapsClonesRegions(t *testing.T) { - src := &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{ - 1: {RegionID: 1, Nodes: []*tailcfg.DERPNode{{Name: "a"}, {Name: "b"}}}, - }, - } - - merged := mergeDERPMaps([]*tailcfg.DERPMap{src}) - - assert.NotSame(t, src.Regions[1], merged.Regions[1], - "merged region must not alias the source region pointer") - - merged.Regions[1].Nodes[0] = &tailcfg.DERPNode{Name: "mutated"} - assert.Equal(t, "a", src.Regions[1].Nodes[0].Name, - "source region was mutated through a shared pointer") -} diff --git a/hscontrol/derp/server/derp_server.go b/hscontrol/derp/server/derp_server.go index e164c608e..fdca651a2 100644 --- a/hscontrol/derp/server/derp_server.go +++ b/hscontrol/derp/server/derp_server.go @@ -76,16 +76,20 @@ func (d *DERPServer) GenerateRegion() (tailcfg.DERPRegion, error) { return tailcfg.DERPRegion{}, err } + var ( + host string + port int + portStr string + ) + // Extract hostname and port from URL - host, portStr, err := net.SplitHostPort(serverURL.Host) - - var port int - + host, portStr, err = net.SplitHostPort(serverURL.Host) if err != nil { - host = serverURL.Host if serverURL.Scheme == "https" { + host = serverURL.Host port = 443 } else { + host = serverURL.Host port = 80 } } else { @@ -367,10 +371,15 @@ func (d *DERPServer) ServeSTUN() { } func serverSTUNListener(ctx context.Context, packetConn *net.UDPConn) { - var buf [64 << 10]byte + var ( + buf [64 << 10]byte + bytesRead int + udpAddr *net.UDPAddr + err error + ) for { - bytesRead, udpAddr, err := packetConn.ReadFromUDP(buf[:]) + bytesRead, udpAddr, err = packetConn.ReadFromUDP(buf[:]) if err != nil { if ctx.Err() != nil { return diff --git a/hscontrol/dns/extrarecords.go b/hscontrol/dns/extrarecords.go index 05f42bc9d..b02a3c270 100644 --- a/hscontrol/dns/extrarecords.go +++ b/hscontrol/dns/extrarecords.go @@ -27,10 +27,10 @@ type ExtraRecordsMan struct { updateCh chan []tailcfg.DNSRecord closeCh chan struct{} - hash [32]byte + hashes map[string][32]byte } -// NewExtraRecordsManager creates a new [ExtraRecordsMan] and starts watching the file at the given path. +// NewExtraRecordsManager creates a new ExtraRecordsMan and starts watching the file at the given path. func NewExtraRecordsManager(path string) (*ExtraRecordsMan, error) { watcher, err := fsnotify.NewWatcher() if err != nil { @@ -52,10 +52,12 @@ func NewExtraRecordsManager(path string) (*ExtraRecordsMan, error) { } er := &ExtraRecordsMan{ - watcher: watcher, - path: path, - records: set.SetOf(records), - hash: hash, + watcher: watcher, + path: path, + records: set.SetOf(records), + hashes: map[string][32]byte{ + path: hash, + }, closeCh: make(chan struct{}), updateCh: make(chan []tailcfg.DNSRecord), } @@ -156,54 +158,44 @@ func (e *ExtraRecordsMan) updateRecords() { } e.mu.Lock() + defer e.mu.Unlock() // If there has not been any change, ignore the update. - if newHash == e.hash { - e.mu.Unlock() - - return + if oldHash, ok := e.hashes[e.path]; ok { + if newHash == oldHash { + return + } } oldCount := e.records.Len() e.records = set.SetOf(records) - e.hash = newHash - toSend := e.records.Slice() + e.hashes[e.path] = newHash log.Trace().Caller().Interface("records", e.records).Msgf("extra records updated from path, count old: %d, new: %d", oldCount, e.records.Len()) - // Release the lock before the (potentially blocking) send so a slow or - // absent consumer cannot stall Records() readers, and abort the send on - // shutdown instead of leaking this goroutine on the closed-down channel. - e.mu.Unlock() - - select { - case e.updateCh <- toSend: - case <-e.closeCh: - } + e.updateCh <- e.records.Slice() } -// readExtraRecordsFromPath reads a JSON file of [tailcfg.DNSRecord] +// readExtraRecordsFromPath reads a JSON file of tailcfg.DNSRecord // and returns the records and the hash of the file. func readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error) { - var zero [32]byte - b, err := os.ReadFile(path) if err != nil { - return nil, zero, fmt.Errorf("reading path: %s, err: %w", path, err) + return nil, [32]byte{}, fmt.Errorf("reading path: %s, err: %w", path, err) } // If the read was triggered too fast, and the file is not complete, ignore the update // if the file is empty. A consecutive update will be triggered when the file is complete. if len(b) == 0 { - return nil, zero, nil + return nil, [32]byte{}, nil } var records []tailcfg.DNSRecord err = json.Unmarshal(b, &records) if err != nil { - return nil, zero, fmt.Errorf("unmarshalling records, content: %q: %w", string(b), err) + return nil, [32]byte{}, fmt.Errorf("unmarshalling records, content: %q: %w", string(b), err) } hash := sha256.Sum256(b) diff --git a/hscontrol/dns/extrarecords_shutdown_test.go b/hscontrol/dns/extrarecords_shutdown_test.go deleted file mode 100644 index 457d58c5e..000000000 --- a/hscontrol/dns/extrarecords_shutdown_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package dns - -import ( - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// TestUpdateRecordsDoesNotBlockShutdown ensures updateRecords does not park -// forever on the update channel during shutdown. The send must release the -// write lock first and abort on closeCh, otherwise the Run goroutine leaks and -// holds the lock indefinitely when no consumer is draining the channel. -func TestUpdateRecordsDoesNotBlockShutdown(t *testing.T) { - path := filepath.Join(t.TempDir(), "extra.json") - require.NoError(t, os.WriteFile(path, - []byte(`[{"name":"a.example.com","type":"A","value":"100.64.0.1"}]`), 0o600)) - - er, err := NewExtraRecordsManager(path) - require.NoError(t, err) - - defer er.watcher.Close() - - // Change the file so updateRecords passes the unchanged-hash guard and - // reaches the send with no consumer draining UpdateCh. - require.NoError(t, os.WriteFile(path, - []byte(`[{"name":"b.example.com","type":"A","value":"100.64.0.2"}]`), 0o600)) - - done := make(chan struct{}) - - go func() { - er.updateRecords() - close(done) - }() - - er.Close() - - select { - case <-done: - case <-time.After(2 * time.Second): - t.Fatal("updateRecords parked on a blocking send and did not return after Close") - } -} diff --git a/hscontrol/grpcv1.go b/hscontrol/grpcv1.go new file mode 100644 index 000000000..953ae0d72 --- /dev/null +++ b/hscontrol/grpcv1.go @@ -0,0 +1,950 @@ +//go:generate buf generate --template ../buf.gen.yaml -o .. ../proto + +// nolint +package hscontrol + +import ( + "context" + "errors" + "fmt" + "io" + "net/netip" + "os" + "slices" + "sort" + "strings" + "time" + + "github.com/rs/zerolog/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + "gorm.io/gorm" + "tailscale.com/net/tsaddr" + "tailscale.com/tailcfg" + "tailscale.com/types/key" + "tailscale.com/types/views" + + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" + "github.com/juanfont/headscale/hscontrol/state" + "github.com/juanfont/headscale/hscontrol/types" + "github.com/juanfont/headscale/hscontrol/util" + "github.com/juanfont/headscale/hscontrol/util/zlog/zf" +) + +type headscaleV1APIServer struct { // v1.HeadscaleServiceServer + v1.UnimplementedHeadscaleServiceServer + h *Headscale +} + +func newHeadscaleV1APIServer(h *Headscale) v1.HeadscaleServiceServer { + return headscaleV1APIServer{ + h: h, + } +} + +func (api headscaleV1APIServer) CreateUser( + ctx context.Context, + request *v1.CreateUserRequest, +) (*v1.CreateUserResponse, error) { + newUser := types.User{ + Name: request.GetName(), + DisplayName: request.GetDisplayName(), + Email: request.GetEmail(), + ProfilePicURL: request.GetPictureUrl(), + } + user, policyChanged, err := api.h.state.CreateUser(newUser) + if err != nil { + return nil, status.Errorf(codes.Internal, "creating user: %s", err) + } + + // CreateUser returns a policy change response if the user creation affected policy. + // This triggers a full policy re-evaluation for all connected nodes. + api.h.Change(policyChanged) + + return &v1.CreateUserResponse{User: user.Proto()}, nil +} + +func (api headscaleV1APIServer) RenameUser( + ctx context.Context, + request *v1.RenameUserRequest, +) (*v1.RenameUserResponse, error) { + oldUser, err := api.h.state.GetUserByID(types.UserID(request.GetOldId())) + if err != nil { + return nil, err + } + + _, c, err := api.h.state.RenameUser(types.UserID(oldUser.ID), request.GetNewName()) + if err != nil { + return nil, err + } + + // Send policy update notifications if needed + api.h.Change(c) + + newUser, err := api.h.state.GetUserByName(request.GetNewName()) + if err != nil { + return nil, err + } + + return &v1.RenameUserResponse{User: newUser.Proto()}, nil +} + +func (api headscaleV1APIServer) DeleteUser( + ctx context.Context, + request *v1.DeleteUserRequest, +) (*v1.DeleteUserResponse, error) { + user, err := api.h.state.GetUserByID(types.UserID(request.GetId())) + if err != nil { + return nil, err + } + + policyChanged, err := api.h.state.DeleteUser(types.UserID(user.ID)) + if err != nil { + return nil, err + } + + // Use the change returned from DeleteUser which includes proper policy updates + api.h.Change(policyChanged) + + return &v1.DeleteUserResponse{}, nil +} + +func (api headscaleV1APIServer) ListUsers( + ctx context.Context, + request *v1.ListUsersRequest, +) (*v1.ListUsersResponse, error) { + var err error + var users []types.User + + switch { + case request.GetName() != "": + users, err = api.h.state.ListUsersWithFilter(&types.User{Name: request.GetName()}) + case request.GetEmail() != "": + users, err = api.h.state.ListUsersWithFilter(&types.User{Email: request.GetEmail()}) + case request.GetId() != 0: + users, err = api.h.state.ListUsersWithFilter(&types.User{Model: gorm.Model{ID: uint(request.GetId())}}) + default: + users, err = api.h.state.ListAllUsers() + } + if err != nil { + return nil, err + } + + response := make([]*v1.User, len(users)) + for index, user := range users { + response[index] = user.Proto() + } + + sort.Slice(response, func(i, j int) bool { + return response[i].Id < response[j].Id + }) + + return &v1.ListUsersResponse{Users: response}, nil +} + +func (api headscaleV1APIServer) CreatePreAuthKey( + ctx context.Context, + request *v1.CreatePreAuthKeyRequest, +) (*v1.CreatePreAuthKeyResponse, error) { + var expiration time.Time + if request.GetExpiration() != nil { + expiration = request.GetExpiration().AsTime() + } + + for _, tag := range request.AclTags { + err := validateTag(tag) + if err != nil { + return &v1.CreatePreAuthKeyResponse{ + PreAuthKey: nil, + }, status.Error(codes.InvalidArgument, err.Error()) + } + } + + var userID *types.UserID + if request.GetUser() != 0 { + user, err := api.h.state.GetUserByID(types.UserID(request.GetUser())) + if err != nil { + return nil, err + } + userID = user.TypedID() + } + + preAuthKey, err := api.h.state.CreatePreAuthKey( + userID, + request.GetReusable(), + request.GetEphemeral(), + &expiration, + request.AclTags, + ) + if err != nil { + return nil, err + } + + return &v1.CreatePreAuthKeyResponse{PreAuthKey: preAuthKey.Proto()}, nil +} + +func (api headscaleV1APIServer) ExpirePreAuthKey( + ctx context.Context, + request *v1.ExpirePreAuthKeyRequest, +) (*v1.ExpirePreAuthKeyResponse, error) { + err := api.h.state.ExpirePreAuthKey(request.GetId()) + if err != nil { + return nil, err + } + + return &v1.ExpirePreAuthKeyResponse{}, nil +} + +func (api headscaleV1APIServer) DeletePreAuthKey( + ctx context.Context, + request *v1.DeletePreAuthKeyRequest, +) (*v1.DeletePreAuthKeyResponse, error) { + err := api.h.state.DeletePreAuthKey(request.GetId()) + if err != nil { + return nil, err + } + + return &v1.DeletePreAuthKeyResponse{}, nil +} + +func (api headscaleV1APIServer) ListPreAuthKeys( + ctx context.Context, + request *v1.ListPreAuthKeysRequest, +) (*v1.ListPreAuthKeysResponse, error) { + preAuthKeys, err := api.h.state.ListPreAuthKeys() + if err != nil { + return nil, err + } + + response := make([]*v1.PreAuthKey, len(preAuthKeys)) + for index, key := range preAuthKeys { + response[index] = key.Proto() + } + + sort.Slice(response, func(i, j int) bool { + return response[i].Id < response[j].Id + }) + + return &v1.ListPreAuthKeysResponse{PreAuthKeys: response}, nil +} + +func (api headscaleV1APIServer) RegisterNode( + ctx context.Context, + request *v1.RegisterNodeRequest, +) (*v1.RegisterNodeResponse, error) { + // Generate ephemeral registration key for tracking this registration flow in logs + registrationKey, err := util.GenerateRegistrationKey() + if err != nil { + log.Warn().Err(err).Msg("failed to generate registration key") + registrationKey = "" // Continue without key if generation fails + } + + log.Trace(). + Caller(). + Str(zf.UserName, request.GetUser()). + Str(zf.RegistrationID, request.GetKey()). + Str(zf.RegistrationKey, registrationKey). + Msg("registering node") + + registrationId, err := types.AuthIDFromString(request.GetKey()) + if err != nil { + return nil, err + } + + user, err := api.h.state.GetUserByName(request.GetUser()) + if err != nil { + return nil, fmt.Errorf("looking up user: %w", err) + } + + node, nodeChange, err := api.h.state.HandleNodeFromAuthPath( + registrationId, + types.UserID(user.ID), + nil, + util.RegisterMethodCLI, + ) + if err != nil { + log.Error(). + Str(zf.RegistrationKey, registrationKey). + Err(err). + Msg("failed to register node") + return nil, err + } + + log.Info(). + Str(zf.RegistrationKey, registrationKey). + EmbedObject(node). + Msg("node registered successfully") + + // This is a bit of a back and forth, but we have a bit of a chicken and egg + // dependency here. + // Because the way the policy manager works, we need to have the node + // in the database, then add it to the policy manager and then we can + // approve the route. This means we get this dance where the node is + // first added to the database, then we add it to the policy manager via + // SaveNode (which automatically updates the policy manager) and then we can auto approve the routes. + // As that only approves the struct object, we need to save it again and + // ensure we send an update. + // This works, but might be another good candidate for doing some sort of + // eventbus. + routeChange, err := api.h.state.AutoApproveRoutes(node) + if err != nil { + return nil, fmt.Errorf("auto approving routes: %w", err) + } + + // Send both changes. Empty changes are ignored by Change(). + api.h.Change(nodeChange, routeChange) + + return &v1.RegisterNodeResponse{Node: node.Proto()}, nil +} + +func (api headscaleV1APIServer) GetNode( + ctx context.Context, + request *v1.GetNodeRequest, +) (*v1.GetNodeResponse, error) { + node, ok := api.h.state.GetNodeByID(types.NodeID(request.GetNodeId())) + if !ok { + return nil, status.Errorf(codes.NotFound, "node not found") + } + + resp := node.Proto() + + return &v1.GetNodeResponse{Node: resp}, nil +} + +func (api headscaleV1APIServer) SetTags( + ctx context.Context, + request *v1.SetTagsRequest, +) (*v1.SetTagsResponse, error) { + // Validate tags not empty - tagged nodes must have at least one tag + if len(request.GetTags()) == 0 { + return &v1.SetTagsResponse{ + Node: nil, + }, status.Error( + codes.InvalidArgument, + "cannot remove all tags from a node - tagged nodes must have at least one tag", + ) + } + + // Validate tag format + for _, tag := range request.GetTags() { + err := validateTag(tag) + if err != nil { + return nil, err + } + } + + // User XOR Tags: nodes are either tagged or user-owned, never both. + // Setting tags on a user-owned node converts it to a tagged node. + // Once tagged, a node cannot be converted back to user-owned. + _, found := api.h.state.GetNodeByID(types.NodeID(request.GetNodeId())) + if !found { + return &v1.SetTagsResponse{ + Node: nil, + }, status.Error(codes.NotFound, "node not found") + } + + node, nodeChange, err := api.h.state.SetNodeTags(types.NodeID(request.GetNodeId()), request.GetTags()) + if err != nil { + return &v1.SetTagsResponse{ + Node: nil, + }, status.Error(codes.InvalidArgument, err.Error()) + } + + api.h.Change(nodeChange) + + log.Trace(). + Caller(). + EmbedObject(node). + Strs("tags", request.GetTags()). + Msg("changing tags of node") + + return &v1.SetTagsResponse{Node: node.Proto()}, nil +} + +func (api headscaleV1APIServer) SetApprovedRoutes( + ctx context.Context, + request *v1.SetApprovedRoutesRequest, +) (*v1.SetApprovedRoutesResponse, error) { + log.Debug(). + Caller(). + Uint64(zf.NodeID, request.GetNodeId()). + Strs("requestedRoutes", request.GetRoutes()). + Msg("gRPC SetApprovedRoutes called") + + var newApproved []netip.Prefix + for _, route := range request.GetRoutes() { + prefix, err := netip.ParsePrefix(route) + if err != nil { + return nil, fmt.Errorf("parsing route: %w", err) + } + + // If the prefix is an exit route, add both. The client expect both + // to annotate the node as an exit node. + if prefix == tsaddr.AllIPv4() || prefix == tsaddr.AllIPv6() { + newApproved = append(newApproved, tsaddr.AllIPv4(), tsaddr.AllIPv6()) + } else { + newApproved = append(newApproved, prefix) + } + } + slices.SortFunc(newApproved, netip.Prefix.Compare) + newApproved = slices.Compact(newApproved) + + node, nodeChange, err := api.h.state.SetApprovedRoutes(types.NodeID(request.GetNodeId()), newApproved) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // Always propagate node changes from SetApprovedRoutes + api.h.Change(nodeChange) + + proto := node.Proto() + // Populate SubnetRoutes with PrimaryRoutes to ensure it includes only the + // routes that are actively served from the node (per architectural requirement in types/node.go) + primaryRoutes := api.h.state.GetNodePrimaryRoutes(node.ID()) + proto.SubnetRoutes = util.PrefixesToString(primaryRoutes) + + log.Debug(). + Caller(). + EmbedObject(node). + Strs("approvedRoutes", util.PrefixesToString(node.ApprovedRoutes().AsSlice())). + Strs("primaryRoutes", util.PrefixesToString(primaryRoutes)). + Strs("finalSubnetRoutes", proto.SubnetRoutes). + Msg("gRPC SetApprovedRoutes completed") + + return &v1.SetApprovedRoutesResponse{Node: proto}, nil +} + +func validateTag(tag string) error { + if strings.Index(tag, "tag:") != 0 { + return errors.New("tag must start with the string 'tag:'") + } + if strings.ToLower(tag) != tag { + return errors.New("tag should be lowercase") + } + if len(strings.Fields(tag)) > 1 { + return errors.New("tags must not contain spaces") + } + return nil +} + +func (api headscaleV1APIServer) DeleteNode( + ctx context.Context, + request *v1.DeleteNodeRequest, +) (*v1.DeleteNodeResponse, error) { + node, ok := api.h.state.GetNodeByID(types.NodeID(request.GetNodeId())) + if !ok { + return nil, status.Errorf(codes.NotFound, "node not found") + } + + nodeChange, err := api.h.state.DeleteNode(node) + if err != nil { + return nil, err + } + + api.h.Change(nodeChange) + + return &v1.DeleteNodeResponse{}, nil +} + +func (api headscaleV1APIServer) ExpireNode( + ctx context.Context, + request *v1.ExpireNodeRequest, +) (*v1.ExpireNodeResponse, error) { + if request.GetDisableExpiry() && request.GetExpiry() != nil { + return nil, status.Error( + codes.InvalidArgument, + "cannot set both disable_expiry and expiry", + ) + } + + // Handle disable expiry request - node will never expire. + if request.GetDisableExpiry() { + node, nodeChange, err := api.h.state.SetNodeExpiry( + types.NodeID(request.GetNodeId()), nil, + ) + if err != nil { + return nil, err + } + + api.h.Change(nodeChange) + + log.Trace(). + Caller(). + EmbedObject(node). + Msg("node expiry disabled") + + return &v1.ExpireNodeResponse{Node: node.Proto()}, nil + } + + expiry := time.Now() + if request.GetExpiry() != nil { + expiry = request.GetExpiry().AsTime() + } + + node, nodeChange, err := api.h.state.SetNodeExpiry( + types.NodeID(request.GetNodeId()), &expiry, + ) + if err != nil { + return nil, err + } + + // TODO(kradalby): Ensure that both the selfupdate and peer updates are sent + api.h.Change(nodeChange) + + log.Trace(). + Caller(). + EmbedObject(node). + Time(zf.ExpiresAt, expiry). + Msg("node expired") + + return &v1.ExpireNodeResponse{Node: node.Proto()}, nil +} + +func (api headscaleV1APIServer) RenameNode( + ctx context.Context, + request *v1.RenameNodeRequest, +) (*v1.RenameNodeResponse, error) { + node, nodeChange, err := api.h.state.RenameNode(types.NodeID(request.GetNodeId()), request.GetNewName()) + if err != nil { + return nil, err + } + + // TODO(kradalby): investigate if we need selfupdate + api.h.Change(nodeChange) + + log.Trace(). + Caller(). + EmbedObject(node). + Str(zf.NewName, request.GetNewName()). + Msg("node renamed") + + return &v1.RenameNodeResponse{Node: node.Proto()}, nil +} + +func (api headscaleV1APIServer) ListNodes( + ctx context.Context, + request *v1.ListNodesRequest, +) (*v1.ListNodesResponse, error) { + // TODO(kradalby): it looks like this can be simplified a lot, + // the filtering of nodes by user, vs nodes as a whole can + // probably be done once. + // TODO(kradalby): This should be done in one tx. + if request.GetUser() != "" { + user, err := api.h.state.GetUserByName(request.GetUser()) + if err != nil { + return nil, err + } + + nodes := api.h.state.ListNodesByUser(types.UserID(user.ID)) + + response := nodesToProto(api.h.state, nodes) + return &v1.ListNodesResponse{Nodes: response}, nil + } + + nodes := api.h.state.ListNodes() + + response := nodesToProto(api.h.state, nodes) + return &v1.ListNodesResponse{Nodes: response}, nil +} + +func nodesToProto(state *state.State, nodes views.Slice[types.NodeView]) []*v1.Node { + response := make([]*v1.Node, nodes.Len()) + for index, node := range nodes.All() { + resp := node.Proto() + + // Tags-as-identity: tagged nodes show as TaggedDevices user in API responses + // (UserID may be set internally for "created by" tracking) + if node.IsTagged() { + resp.User = types.TaggedDevices.Proto() + } + + resp.SubnetRoutes = util.PrefixesToString(append(state.GetNodePrimaryRoutes(node.ID()), node.ExitRoutes()...)) + response[index] = resp + } + + sort.Slice(response, func(i, j int) bool { + return response[i].Id < response[j].Id + }) + + return response +} + +func (api headscaleV1APIServer) BackfillNodeIPs( + ctx context.Context, + request *v1.BackfillNodeIPsRequest, +) (*v1.BackfillNodeIPsResponse, error) { + log.Trace().Caller().Msg("backfill called") + + if !request.Confirmed { + return nil, errors.New("not confirmed, aborting") + } + + changes, err := api.h.state.BackfillNodeIPs() + if err != nil { + return nil, err + } + + return &v1.BackfillNodeIPsResponse{Changes: changes}, nil +} + +func (api headscaleV1APIServer) CreateApiKey( + ctx context.Context, + request *v1.CreateApiKeyRequest, +) (*v1.CreateApiKeyResponse, error) { + var expiration time.Time + if request.GetExpiration() != nil { + expiration = request.GetExpiration().AsTime() + } + + apiKey, _, err := api.h.state.CreateAPIKey(&expiration) + if err != nil { + return nil, err + } + + return &v1.CreateApiKeyResponse{ApiKey: apiKey}, nil +} + +// apiKeyIdentifier is implemented by requests that identify an API key. +type apiKeyIdentifier interface { + GetId() uint64 + GetPrefix() string +} + +// getAPIKey retrieves an API key by ID or prefix from the request. +// Returns InvalidArgument if neither or both are provided. +func (api headscaleV1APIServer) getAPIKey(req apiKeyIdentifier) (*types.APIKey, error) { + hasID := req.GetId() != 0 + hasPrefix := req.GetPrefix() != "" + + switch { + case hasID && hasPrefix: + return nil, status.Error(codes.InvalidArgument, "provide either id or prefix, not both") + case hasID: + return api.h.state.GetAPIKeyByID(req.GetId()) + case hasPrefix: + return api.h.state.GetAPIKey(req.GetPrefix()) + default: + return nil, status.Error(codes.InvalidArgument, "must provide id or prefix") + } +} + +func (api headscaleV1APIServer) ExpireApiKey( + ctx context.Context, + request *v1.ExpireApiKeyRequest, +) (*v1.ExpireApiKeyResponse, error) { + apiKey, err := api.getAPIKey(request) + if err != nil { + return nil, err + } + + err = api.h.state.ExpireAPIKey(apiKey) + if err != nil { + return nil, err + } + + return &v1.ExpireApiKeyResponse{}, nil +} + +func (api headscaleV1APIServer) ListApiKeys( + ctx context.Context, + request *v1.ListApiKeysRequest, +) (*v1.ListApiKeysResponse, error) { + apiKeys, err := api.h.state.ListAPIKeys() + if err != nil { + return nil, err + } + + response := make([]*v1.ApiKey, len(apiKeys)) + for index, key := range apiKeys { + response[index] = key.Proto() + } + + sort.Slice(response, func(i, j int) bool { + return response[i].Id < response[j].Id + }) + + return &v1.ListApiKeysResponse{ApiKeys: response}, nil +} + +func (api headscaleV1APIServer) DeleteApiKey( + ctx context.Context, + request *v1.DeleteApiKeyRequest, +) (*v1.DeleteApiKeyResponse, error) { + apiKey, err := api.getAPIKey(request) + if err != nil { + return nil, err + } + + if err := api.h.state.DestroyAPIKey(*apiKey); err != nil { + return nil, err + } + + return &v1.DeleteApiKeyResponse{}, nil +} + +func (api headscaleV1APIServer) GetPolicy( + _ context.Context, + _ *v1.GetPolicyRequest, +) (*v1.GetPolicyResponse, error) { + switch api.h.cfg.Policy.Mode { + case types.PolicyModeDB: + p, err := api.h.state.GetPolicy() + if err != nil { + return nil, fmt.Errorf("loading ACL from database: %w", err) + } + + return &v1.GetPolicyResponse{ + Policy: p.Data, + UpdatedAt: timestamppb.New(p.UpdatedAt), + }, nil + case types.PolicyModeFile: + // Read the file and return the contents as-is. + absPath := util.AbsolutePathFromConfigPath(api.h.cfg.Policy.Path) + f, err := os.Open(absPath) + if err != nil { + return nil, fmt.Errorf("reading policy from path %q: %w", absPath, err) + } + + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("reading policy from file: %w", err) + } + + return &v1.GetPolicyResponse{Policy: string(b)}, nil + } + + return nil, fmt.Errorf("no supported policy mode found in configuration, policy.mode: %q", api.h.cfg.Policy.Mode) +} + +func (api headscaleV1APIServer) SetPolicy( + _ context.Context, + request *v1.SetPolicyRequest, +) (*v1.SetPolicyResponse, error) { + if api.h.cfg.Policy.Mode != types.PolicyModeDB { + return nil, types.ErrPolicyUpdateIsDisabled + } + + p := request.GetPolicy() + + // Validate and reject configuration that would error when applied + // when creating a map response. This requires nodes, so there is still + // a scenario where they might be allowed if the server has no nodes + // yet, but it should help for the general case and for hot reloading + // configurations. + nodes := api.h.state.ListNodes() + + _, err := api.h.state.SetPolicy([]byte(p)) + if err != nil { + return nil, fmt.Errorf("setting policy: %w", err) + } + + if nodes.Len() > 0 { + _, err = api.h.state.SSHPolicy(nodes.At(0)) + if err != nil { + return nil, fmt.Errorf("verifying SSH rules: %w", err) + } + } + + updated, err := api.h.state.SetPolicyInDB(p) + if err != nil { + return nil, err + } + + // Always reload policy to ensure route re-evaluation, even if policy content hasn't changed. + // This ensures that routes are re-evaluated for auto-approval in cases where routes + // were manually disabled but could now be auto-approved with the current policy. + cs, err := api.h.state.ReloadPolicy() + if err != nil { + return nil, fmt.Errorf("reloading policy: %w", err) + } + + if len(cs) > 0 { + api.h.Change(cs...) + } else { + log.Debug(). + Caller(). + Msg("No policy changes to distribute because ReloadPolicy returned empty changeset") + } + + response := &v1.SetPolicyResponse{ + Policy: updated.Data, + UpdatedAt: timestamppb.New(updated.UpdatedAt), + } + + log.Debug(). + Caller(). + Msg("gRPC SetPolicy completed successfully because response prepared") + + return response, nil +} + +// CheckPolicy validates the given policy against the server's live users +// and nodes, running its `tests` block as a sandbox. Nothing is persisted +// and the live PolicyManager is not touched. Works regardless of +// policy.mode so operators can validate a policy file before storing it. +func (api headscaleV1APIServer) CheckPolicy( + _ context.Context, + request *v1.CheckPolicyRequest, +) (*v1.CheckPolicyResponse, error) { + polB := []byte(request.GetPolicy()) + + users, err := api.h.state.ListAllUsers() + if err != nil { + return nil, status.Errorf(codes.Internal, "loading users: %s", err) + } + + nodes := api.h.state.ListNodes() + + pm, err := policyv2.NewPolicyManager(polB, users, nodes) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + if _, err := pm.SetPolicy(polB); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + return &v1.CheckPolicyResponse{}, nil +} + +// The following service calls are for testing and debugging +func (api headscaleV1APIServer) DebugCreateNode( + ctx context.Context, + request *v1.DebugCreateNodeRequest, +) (*v1.DebugCreateNodeResponse, error) { + user, err := api.h.state.GetUserByName(request.GetUser()) + if err != nil { + return nil, err + } + + routes, err := util.StringToIPPrefix(request.GetRoutes()) + if err != nil { + return nil, err + } + + log.Trace(). + Caller(). + Interface("route-prefix", routes). + Interface("route-str", request.GetRoutes()). + Msg("Creating routes for node") + + registrationId, err := types.AuthIDFromString(request.GetKey()) + if err != nil { + return nil, err + } + + regData := &types.RegistrationData{ + NodeKey: key.NewNode().Public(), + MachineKey: key.NewMachine().Public(), + Hostname: request.GetName(), + Expiry: &time.Time{}, // zero time, not nil — preserves proto JSON round-trip semantics + } + + log.Debug(). + Caller(). + Str("registration_id", registrationId.String()). + Msg("adding debug machine via CLI, appending to registration cache") + + authRegReq := types.NewRegisterAuthRequest(regData) + api.h.state.SetAuthCacheEntry(registrationId, authRegReq) + + // Echo back a synthetic Node so the debug response surface stays + // stable. The actual node is created later by AuthApprove via + // HandleNodeFromAuthPath using the cached RegistrationData. + echoNode := types.Node{ + NodeKey: regData.NodeKey, + MachineKey: regData.MachineKey, + Hostname: regData.Hostname, + User: user, + Expiry: &time.Time{}, + LastSeen: &time.Time{}, + Hostinfo: &tailcfg.Hostinfo{ + Hostname: request.GetName(), + OS: "TestOS", + RoutableIPs: routes, + }, + } + + return &v1.DebugCreateNodeResponse{Node: echoNode.Proto()}, nil +} + +func (api headscaleV1APIServer) Health( + ctx context.Context, + request *v1.HealthRequest, +) (*v1.HealthResponse, error) { + var healthErr error + response := &v1.HealthResponse{} + + if err := api.h.state.PingDB(ctx); err != nil { + healthErr = fmt.Errorf("pinging database: %w", err) + } else { + response.DatabaseConnectivity = true + } + + if healthErr != nil { + log.Error().Err(healthErr).Msg("health check failed") + } + + return response, healthErr +} + +func (api headscaleV1APIServer) AuthRegister( + ctx context.Context, + request *v1.AuthRegisterRequest, +) (*v1.AuthRegisterResponse, error) { + resp, err := api.RegisterNode(ctx, &v1.RegisterNodeRequest{ + Key: request.GetAuthId(), + User: request.GetUser(), + }) + if err != nil { + return nil, err + } + + return &v1.AuthRegisterResponse{Node: resp.GetNode()}, nil +} + +func (api headscaleV1APIServer) AuthApprove( + ctx context.Context, + request *v1.AuthApproveRequest, +) (*v1.AuthApproveResponse, error) { + authID, err := types.AuthIDFromString(request.GetAuthId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid auth_id: %v", err) + } + + authReq, ok := api.h.state.GetAuthCacheEntry(authID) + if !ok { + return nil, status.Errorf(codes.NotFound, "no pending auth session for auth_id %s", authID) + } + + authReq.FinishAuth(types.AuthVerdict{}) + + return &v1.AuthApproveResponse{}, nil +} + +func (api headscaleV1APIServer) AuthReject( + ctx context.Context, + request *v1.AuthRejectRequest, +) (*v1.AuthRejectResponse, error) { + authID, err := types.AuthIDFromString(request.GetAuthId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid auth_id: %v", err) + } + + authReq, ok := api.h.state.GetAuthCacheEntry(authID) + if !ok { + return nil, status.Errorf(codes.NotFound, "no pending auth session for auth_id %s", authID) + } + + authReq.FinishAuth(types.AuthVerdict{ + Err: fmt.Errorf("auth request rejected"), + }) + + return &v1.AuthRejectResponse{}, nil +} + +func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {} diff --git a/hscontrol/grpcv1_test.go b/hscontrol/grpcv1_test.go new file mode 100644 index 000000000..bd4289cb7 --- /dev/null +++ b/hscontrol/grpcv1_test.go @@ -0,0 +1,823 @@ +package hscontrol + +import ( + "context" + "testing" + "time" + + v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "tailscale.com/tailcfg" + "tailscale.com/types/key" +) + +func Test_validateTag(t *testing.T) { + type args struct { + tag string + } + + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "valid tag", + args: args{tag: "tag:test"}, + wantErr: false, + }, + { + name: "tag without tag prefix", + args: args{tag: "test"}, + wantErr: true, + }, + { + name: "uppercase tag", + args: args{tag: "tag:tEST"}, + wantErr: true, + }, + { + name: "tag that contains space", + args: args{tag: "tag:this is a spaced tag"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateTag(tt.args.tag) + if (err != nil) != tt.wantErr { + t.Errorf("validateTag() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// TestSetTags_Conversion tests the conversion of user-owned nodes to tagged nodes. +// The tags-as-identity model allows one-way conversion from user-owned to tagged. +// Tag authorization is checked via the policy manager - unauthorized tags are rejected. +func TestSetTags_Conversion(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + // Create test user and nodes + user := app.state.CreateUserForTest("test-user") + + // Create a pre-auth key WITHOUT tags for user-owned node + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + machineKey1 := key.NewMachine() + nodeKey1 := key.NewNode() + + // Register a user-owned node (via untagged PreAuthKey) + userOwnedReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey1.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "user-owned-node", + }, + } + _, err = app.handleRegisterWithAuthKey(userOwnedReq, machineKey1.Public()) + require.NoError(t, err) + + // Get the created node + userOwnedNode, found := app.state.GetNodeByNodeKey(nodeKey1.Public()) + require.True(t, found) + + // Create API server instance + apiServer := newHeadscaleV1APIServer(app) + + tests := []struct { + name string + nodeID uint64 + tags []string + wantErr bool + wantCode codes.Code + wantErrMessage string + }{ + { + // Conversion is allowed, but tag authorization fails without tagOwners + name: "reject unauthorized tags on user-owned node", + nodeID: uint64(userOwnedNode.ID()), + tags: []string{"tag:server"}, + wantErr: true, + wantCode: codes.InvalidArgument, + wantErrMessage: "requested tags", + }, + { + // Conversion is allowed, but tag authorization fails without tagOwners + name: "reject multiple unauthorized tags", + nodeID: uint64(userOwnedNode.ID()), + tags: []string{"tag:server", "tag:database"}, + wantErr: true, + wantCode: codes.InvalidArgument, + wantErrMessage: "requested tags", + }, + { + name: "reject non-existent node", + nodeID: 99999, + tags: []string{"tag:server"}, + wantErr: true, + wantCode: codes.NotFound, + wantErrMessage: "node not found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + resp, err := apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: tt.nodeID, + Tags: tt.tags, + }) + + if tt.wantErr { + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, tt.wantCode, st.Code()) + assert.Contains(t, st.Message(), tt.wantErrMessage) + assert.Nil(t, resp.GetNode()) + } else { + require.NoError(t, err) + assert.NotNil(t, resp) + assert.NotNil(t, resp.GetNode()) + } + }) + } +} + +// TestSetTags_TaggedNode tests that SetTags correctly identifies tagged nodes +// and doesn't reject them with the "user-owned nodes" error. +// Note: This test doesn't validate ACL tag authorization - that's tested elsewhere. +func TestSetTags_TaggedNode(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + // Create test user and tagged pre-auth key + user := app.state.CreateUserForTest("test-user") + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, []string{"tag:initial"}) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + // Register a tagged node (via tagged PreAuthKey) + taggedReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "tagged-node", + }, + } + _, err = app.handleRegisterWithAuthKey(taggedReq, machineKey.Public()) + require.NoError(t, err) + + // Get the created node + taggedNode, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + assert.True(t, taggedNode.IsTagged(), "Node should be tagged") + assert.False(t, taggedNode.UserID().Valid(), "Tagged node should not have UserID") + + // Create API server instance + apiServer := newHeadscaleV1APIServer(app) + + // Test: SetTags should work on tagged nodes. + resp, err := apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(taggedNode.ID()), + Tags: []string{"tag:initial"}, // Keep existing tag to avoid ACL validation issues + }) + + // The call should NOT fail with "cannot set tags on user-owned nodes" + if err != nil { + st, ok := status.FromError(err) + require.True(t, ok) + // If error is about unauthorized tags, that's fine - ACL validation is working + // If error is about user-owned nodes, that's the bug we're testing for + assert.NotContains(t, st.Message(), "user-owned nodes", "Should not reject tagged nodes as user-owned") + } else { + // Success is also fine + assert.NotNil(t, resp) + } +} + +// TestSetTags_CannotRemoveAllTags tests that SetTags rejects attempts to remove +// all tags from a tagged node, enforcing Tailscale's requirement that tagged +// nodes must have at least one tag. +func TestSetTags_CannotRemoveAllTags(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + // Create test user and tagged pre-auth key + user := app.state.CreateUserForTest("test-user") + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, []string{"tag:server"}) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + // Register a tagged node + taggedReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "tagged-node", + }, + } + _, err = app.handleRegisterWithAuthKey(taggedReq, machineKey.Public()) + require.NoError(t, err) + + // Get the created node + taggedNode, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + assert.True(t, taggedNode.IsTagged()) + + // Create API server instance + apiServer := newHeadscaleV1APIServer(app) + + // Attempt to remove all tags (empty array) + resp, err := apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(taggedNode.ID()), + Tags: []string{}, // Empty - attempting to remove all tags + }) + + // Should fail with InvalidArgument error + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code()) + assert.Contains(t, st.Message(), "cannot remove all tags") + assert.Nil(t, resp.GetNode()) +} + +// TestSetTags_ClearsUserIDInDatabase tests that converting a user-owned node +// to a tagged node via SetTags correctly persists user_id = NULL in the +// database, not just in-memory. +// https://github.com/juanfont/headscale/issues/3161 +func TestSetTags_ClearsUserIDInDatabase(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + user := app.state.CreateUserForTest("tag-owner") + err := app.state.UpdatePolicyManagerUsersForTest() + require.NoError(t, err) + + _, err = app.state.SetPolicy([]byte(`{ + "tagOwners": {"tag:server": ["tag-owner@"]}, + "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] + }`)) + require.NoError(t, err) + + // Register a user-owned node (untagged PreAuthKey). + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + regReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "user-owned-node", + }, + } + _, err = app.handleRegisterWithAuthKey(regReq, machineKey.Public()) + require.NoError(t, err) + + node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + require.False(t, node.IsTagged(), "node should start as user-owned") + require.True(t, node.UserID().Valid(), "user-owned node must have UserID") + + nodeID := node.ID() + + // Convert to tagged via SetTags API. + apiServer := newHeadscaleV1APIServer(app) + _, err = apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(nodeID), + Tags: []string{"tag:server"}, + }) + require.NoError(t, err) + + // Verify in-memory state is correct. + nsNode, found := app.state.GetNodeByID(nodeID) + require.True(t, found) + assert.True(t, nsNode.IsTagged(), "NodeStore: node should be tagged") + assert.False(t, nsNode.UserID().Valid(), + "NodeStore: UserID should be nil for tagged node") + + // THE CRITICAL CHECK: verify database has user_id = NULL. + dbNode, err := app.state.DB().GetNodeByID(nodeID) + require.NoError(t, err) + assert.Nil(t, dbNode.UserID, + "Database: user_id must be NULL after converting to tagged node") + assert.True(t, dbNode.IsTagged(), + "Database: tags must be set") +} + +// TestSetTags_NodeDisappearsFromUserListing tests issue #3161: +// after converting a user-owned node to tagged, it must no longer appear +// when listing nodes filtered by the original user. +// https://github.com/juanfont/headscale/issues/3161 +func TestSetTags_NodeDisappearsFromUserListing(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + user := app.state.CreateUserForTest("list-user") + err := app.state.UpdatePolicyManagerUsersForTest() + require.NoError(t, err) + + _, err = app.state.SetPolicy([]byte(`{ + "tagOwners": {"tag:web": ["list-user@"]}, + "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] + }`)) + require.NoError(t, err) + + // Register a user-owned node. + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + regReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "web-server", + }, + } + _, err = app.handleRegisterWithAuthKey(regReq, machineKey.Public()) + require.NoError(t, err) + + node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + + // Verify node appears under user before tagging. + apiServer := newHeadscaleV1APIServer(app) + resp, err := apiServer.ListNodes(context.Background(), &v1.ListNodesRequest{ + User: "list-user", + }) + require.NoError(t, err) + assert.Len(t, resp.GetNodes(), 1, "user-owned node should appear under user") + + // Convert to tagged. + _, err = apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(node.ID()), + Tags: []string{"tag:web"}, + }) + require.NoError(t, err) + + // Node must NOT appear when listing by original user. + resp, err = apiServer.ListNodes(context.Background(), &v1.ListNodesRequest{ + User: "list-user", + }) + require.NoError(t, err) + assert.Empty(t, resp.GetNodes(), + "tagged node must not appear when listing nodes for original user") + + // Node must still appear in unfiltered listing. + allResp, err := apiServer.ListNodes(context.Background(), &v1.ListNodesRequest{}) + require.NoError(t, err) + require.Len(t, allResp.GetNodes(), 1) + assert.Contains(t, allResp.GetNodes()[0].GetTags(), "tag:web") +} + +// TestSetTags_NodeStoreAndDBConsistency verifies that after SetTags, the +// in-memory NodeStore and the database agree on the node's ownership state. +// https://github.com/juanfont/headscale/issues/3161 +func TestSetTags_NodeStoreAndDBConsistency(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + user := app.state.CreateUserForTest("consistency-user") + err := app.state.UpdatePolicyManagerUsersForTest() + require.NoError(t, err) + + _, err = app.state.SetPolicy([]byte(`{ + "tagOwners": {"tag:db": ["consistency-user@"]}, + "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] + }`)) + require.NoError(t, err) + + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + regReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "db-node", + }, + } + _, err = app.handleRegisterWithAuthKey(regReq, machineKey.Public()) + require.NoError(t, err) + + node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + + nodeID := node.ID() + + // Convert to tagged. + apiServer := newHeadscaleV1APIServer(app) + _, err = apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(nodeID), + Tags: []string{"tag:db"}, + }) + require.NoError(t, err) + + // In-memory state. + nsNode, found := app.state.GetNodeByID(nodeID) + require.True(t, found) + + // Database state. + dbNode, err := app.state.DB().GetNodeByID(nodeID) + require.NoError(t, err) + + // Both must agree: tagged, no UserID. + assert.True(t, nsNode.IsTagged(), "NodeStore: should be tagged") + assert.True(t, dbNode.IsTagged(), "Database: should be tagged") + + assert.False(t, nsNode.UserID().Valid(), + "NodeStore: UserID should be nil") + assert.Nil(t, dbNode.UserID, + "Database: user_id should be NULL") + + assert.Equal(t, + nsNode.UserID().Valid(), + dbNode.UserID != nil, + "NodeStore and database must agree on UserID state") +} + +// TestSetTags_UserDeletionDoesNotCascadeToTaggedNode tests that deleting the +// original user does not cascade-delete a node that was converted to tagged +// via SetTags. This catches the real-world consequence of stale user_id: +// ON DELETE CASCADE would destroy the tagged node. +// https://github.com/juanfont/headscale/issues/3161 +func TestSetTags_UserDeletionDoesNotCascadeToTaggedNode(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + user := app.state.CreateUserForTest("doomed-user") + err := app.state.UpdatePolicyManagerUsersForTest() + require.NoError(t, err) + + _, err = app.state.SetPolicy([]byte(`{ + "tagOwners": {"tag:survivor": ["doomed-user@"]}, + "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] + }`)) + require.NoError(t, err) + + pak, err := app.state.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + regReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "survivor-node", + }, + } + _, err = app.handleRegisterWithAuthKey(regReq, machineKey.Public()) + require.NoError(t, err) + + node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + + nodeID := node.ID() + + // Convert to tagged. + apiServer := newHeadscaleV1APIServer(app) + _, err = apiServer.SetTags(context.Background(), &v1.SetTagsRequest{ + NodeId: uint64(nodeID), + Tags: []string{"tag:survivor"}, + }) + require.NoError(t, err) + + // Delete the original user. + _, err = app.state.DeleteUser(*user.TypedID()) + require.NoError(t, err) + + // The tagged node must survive in both NodeStore and database. + nsNode, found := app.state.GetNodeByID(nodeID) + require.True(t, found, "tagged node must survive user deletion in NodeStore") + assert.True(t, nsNode.IsTagged()) + + dbNode, err := app.state.DB().GetNodeByID(nodeID) + require.NoError(t, err, "tagged node must survive user deletion in database") + assert.True(t, dbNode.IsTagged()) + assert.Nil(t, dbNode.UserID) +} + +// TestDeleteUser_ReturnsProperChangeSignal tests issue #2967 fix: +// When a user is deleted, the state should return a non-empty change signal +// to ensure policy manager is updated and clients are notified immediately. +func TestDeleteUser_ReturnsProperChangeSignal(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + // Create a user + user := app.state.CreateUserForTest("test-user-to-delete") + require.NotNil(t, user) + + // Delete the user and verify a non-empty change is returned + // Issue #2967: Without the fix, DeleteUser returned an empty change, + // causing stale policy state until another user operation triggered an update. + changeSignal, err := app.state.DeleteUser(*user.TypedID()) + require.NoError(t, err, "DeleteUser should succeed") + assert.False(t, changeSignal.IsEmpty(), "DeleteUser should return a non-empty change signal (issue #2967)") +} + +// TestDeleteUser_TaggedNodeSurvives tests that deleting a user succeeds when +// the user's only nodes are tagged, and that those nodes remain in the +// NodeStore with nil UserID. +// https://github.com/juanfont/headscale/issues/3077 +func TestDeleteUser_TaggedNodeSurvives(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + + user := app.state.CreateUserForTest("legacy-user") + + // Register a tagged node via the full auth flow. + tags := []string{"tag:server"} + pak, err := app.state.CreatePreAuthKey(user.TypedID(), true, false, nil, tags) + require.NoError(t, err) + + machineKey := key.NewMachine() + nodeKey := key.NewNode() + + regReq := tailcfg.RegisterRequest{ + Auth: &tailcfg.RegisterResponseAuth{ + AuthKey: pak.Key, + }, + NodeKey: nodeKey.Public(), + Hostinfo: &tailcfg.Hostinfo{ + Hostname: "tagged-server", + }, + Expiry: time.Now().Add(24 * time.Hour), + } + + resp, err := app.handleRegisterWithAuthKey(regReq, machineKey.Public()) + require.NoError(t, err) + require.True(t, resp.MachineAuthorized) + + // Verify the registered node has nil UserID (enforced at registration). + node, found := app.state.GetNodeByNodeKey(nodeKey.Public()) + require.True(t, found) + require.True(t, node.IsTagged()) + assert.False(t, node.UserID().Valid(), + "tagged node should have nil UserID after registration") + + nodeID := node.ID() + + // NodeStore should not list the tagged node under any user. + nodesForUser := app.state.ListNodesByUser(types.UserID(user.ID)) + assert.Equal(t, 0, nodesForUser.Len(), + "tagged nodes should not appear in nodesByUser index") + + // Delete the user. + changeSignal, err := app.state.DeleteUser(*user.TypedID()) + require.NoError(t, err) + assert.False(t, changeSignal.IsEmpty()) + + // Tagged node survives in the NodeStore. + nodeAfter, found := app.state.GetNodeByID(nodeID) + require.True(t, found, "tagged node should survive user deletion") + assert.True(t, nodeAfter.IsTagged()) + assert.False(t, nodeAfter.UserID().Valid()) + + // Tagged node appears in the global list. + allNodes := app.state.ListNodes() + foundInAll := false + + for _, n := range allNodes.All() { + if n.ID() == nodeID { + foundInAll = true + + break + } + } + + assert.True(t, foundInAll, "tagged node should appear in the global node list") +} + +// TestExpireApiKey_ByID tests that API keys can be expired by ID. +func TestExpireApiKey_ByID(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + // Create an API key + createResp, err := apiServer.CreateApiKey(context.Background(), &v1.CreateApiKeyRequest{}) + require.NoError(t, err) + require.NotEmpty(t, createResp.GetApiKey()) + + // List keys to get the ID + listResp, err := apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + require.Len(t, listResp.GetApiKeys(), 1) + + keyID := listResp.GetApiKeys()[0].GetId() + + // Expire by ID + _, err = apiServer.ExpireApiKey(context.Background(), &v1.ExpireApiKeyRequest{ + Id: keyID, + }) + require.NoError(t, err) + + // Verify key is expired (expiration is set to now or in the past) + listResp, err = apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + require.Len(t, listResp.GetApiKeys(), 1) + assert.NotNil(t, listResp.GetApiKeys()[0].GetExpiration(), "expiration should be set") +} + +// TestExpireApiKey_ByPrefix tests that API keys can still be expired by prefix. +func TestExpireApiKey_ByPrefix(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + // Create an API key + createResp, err := apiServer.CreateApiKey(context.Background(), &v1.CreateApiKeyRequest{}) + require.NoError(t, err) + require.NotEmpty(t, createResp.GetApiKey()) + + // List keys to get the prefix + listResp, err := apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + require.Len(t, listResp.GetApiKeys(), 1) + + keyPrefix := listResp.GetApiKeys()[0].GetPrefix() + + // Expire by prefix + _, err = apiServer.ExpireApiKey(context.Background(), &v1.ExpireApiKeyRequest{ + Prefix: keyPrefix, + }) + require.NoError(t, err) +} + +// TestDeleteApiKey_ByID tests that API keys can be deleted by ID. +func TestDeleteApiKey_ByID(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + // Create an API key + createResp, err := apiServer.CreateApiKey(context.Background(), &v1.CreateApiKeyRequest{}) + require.NoError(t, err) + require.NotEmpty(t, createResp.GetApiKey()) + + // List keys to get the ID + listResp, err := apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + require.Len(t, listResp.GetApiKeys(), 1) + + keyID := listResp.GetApiKeys()[0].GetId() + + // Delete by ID + _, err = apiServer.DeleteApiKey(context.Background(), &v1.DeleteApiKeyRequest{ + Id: keyID, + }) + require.NoError(t, err) + + // Verify key is deleted + listResp, err = apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + assert.Empty(t, listResp.GetApiKeys()) +} + +// TestDeleteApiKey_ByPrefix tests that API keys can still be deleted by prefix. +func TestDeleteApiKey_ByPrefix(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + // Create an API key + createResp, err := apiServer.CreateApiKey(context.Background(), &v1.CreateApiKeyRequest{}) + require.NoError(t, err) + require.NotEmpty(t, createResp.GetApiKey()) + + // List keys to get the prefix + listResp, err := apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + require.Len(t, listResp.GetApiKeys(), 1) + + keyPrefix := listResp.GetApiKeys()[0].GetPrefix() + + // Delete by prefix + _, err = apiServer.DeleteApiKey(context.Background(), &v1.DeleteApiKeyRequest{ + Prefix: keyPrefix, + }) + require.NoError(t, err) + + // Verify key is deleted + listResp, err = apiServer.ListApiKeys(context.Background(), &v1.ListApiKeysRequest{}) + require.NoError(t, err) + assert.Empty(t, listResp.GetApiKeys()) +} + +// TestExpireApiKey_NoIdentifier tests that an error is returned when neither ID nor prefix is provided. +func TestExpireApiKey_NoIdentifier(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + _, err := apiServer.ExpireApiKey(context.Background(), &v1.ExpireApiKeyRequest{}) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code()) + assert.Contains(t, st.Message(), "must provide id or prefix") +} + +// TestDeleteApiKey_NoIdentifier tests that an error is returned when neither ID nor prefix is provided. +func TestDeleteApiKey_NoIdentifier(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + _, err := apiServer.DeleteApiKey(context.Background(), &v1.DeleteApiKeyRequest{}) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code()) + assert.Contains(t, st.Message(), "must provide id or prefix") +} + +// TestExpireApiKey_BothIdentifiers tests that an error is returned when both ID and prefix are provided. +func TestExpireApiKey_BothIdentifiers(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + _, err := apiServer.ExpireApiKey(context.Background(), &v1.ExpireApiKeyRequest{ + Id: 1, + Prefix: "test", + }) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code()) + assert.Contains(t, st.Message(), "provide either id or prefix, not both") +} + +// TestDeleteApiKey_BothIdentifiers tests that an error is returned when both ID and prefix are provided. +func TestDeleteApiKey_BothIdentifiers(t *testing.T) { + t.Parallel() + + app := createTestApp(t) + apiServer := newHeadscaleV1APIServer(app) + + _, err := apiServer.DeleteApiKey(context.Background(), &v1.DeleteApiKeyRequest{ + Id: 1, + Prefix: "test", + }) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "error should be a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code()) + assert.Contains(t, st.Message(), "provide either id or prefix, not both") +} diff --git a/hscontrol/handlers.go b/hscontrol/handlers.go index 242673b38..881079038 100644 --- a/hscontrol/handlers.go +++ b/hscontrol/handlers.go @@ -129,7 +129,7 @@ func parseCapabilityVersion(req *http.Request) (tailcfg.CapabilityVersion, error } // verifyBodyLimit caps the request body for /verify. The DERP verify -// protocol payload ([tailcfg.DERPAdmitClientRequest]) is a few hundred +// protocol payload (tailcfg.DERPAdmitClientRequest) is a few hundred // bytes; 4 KiB is generous and prevents an unauthenticated client from // OOMing the public router with arbitrarily large POSTs. const verifyBodyLimit int64 = 4 * 1024 @@ -148,12 +148,20 @@ func (h *Headscale) handleVerifyRequest( return NewHTTPError(http.StatusBadRequest, "Bad Request: invalid JSON", fmt.Errorf("parsing DERP client request: %w", err)) } - allow := h.state.ListNodes().ContainsFunc(func(n types.NodeView) bool { - return n.NodeKey() == derpAdmitClientRequest.NodePublic - }) + nodes := h.state.ListNodes() + + // Check if any node has the requested NodeKey + var nodeKeyFound bool + + for _, node := range nodes.All() { + if node.NodeKey() == derpAdmitClientRequest.NodePublic { + nodeKeyFound = true + break + } + } resp := &tailcfg.DERPAdmitClientResponse{ - Allow: allow, + Allow: nodeKeyFound, } return json.NewEncoder(writer).Encode(resp) @@ -172,18 +180,13 @@ func (h *Headscale) VerifyHandler( req.Body = http.MaxBytesReader(writer, req.Body, verifyBodyLimit) - // Set the Content-Type before any body byte is written. The first - // Write in handleVerifyRequest triggers an implicit WriteHeader that - // snapshots the header map, so setting it afterwards is a no-op. The - // error path resets the Content-Type via http.Error, so error - // responses remain text/plain. - writer.Header().Set("Content-Type", "application/json") - err := h.handleVerifyRequest(req, writer) if err != nil { httpError(writer, err) return } + + writer.Header().Set("Content-Type", "application/json") } // KeyHandler provides the Headscale pub key @@ -297,23 +300,18 @@ func NewAuthProviderWeb(serverURL string) *AuthProviderWeb { } } -// authPathURL builds an auth-flow URL of the form -// "//", trimming a trailing slash from serverURL. -func authPathURL(serverURL, kind string, authID types.AuthID) string { - return fmt.Sprintf( - "%s/%s/%s", - strings.TrimSuffix(serverURL, "/"), - kind, - authID.String(), - ) -} - func (a *AuthProviderWeb) RegisterURL(authID types.AuthID) string { - return authPathURL(a.serverURL, "register", authID) + return fmt.Sprintf( + "%s/register/%s", + strings.TrimSuffix(a.serverURL, "/"), + authID.String()) } func (a *AuthProviderWeb) AuthURL(authID types.AuthID) string { - return authPathURL(a.serverURL, "auth", authID) + return fmt.Sprintf( + "%s/auth/%s", + strings.TrimSuffix(a.serverURL, "/"), + authID.String()) } func (a *AuthProviderWeb) AuthHandler( @@ -340,7 +338,7 @@ func (a *AuthProviderWeb) AuthHandler( } func authIDFromRequest(req *http.Request) (types.AuthID, error) { - raw, err := stringParam(req, "auth_id") + raw, err := urlParam[string](req, "auth_id") if err != nil { return "", NewHTTPError(http.StatusBadRequest, "invalid auth id", fmt.Errorf("parsing auth_id from URL: %w", err)) } @@ -360,7 +358,7 @@ func authIDFromRequest(req *http.Request) (types.AuthID, error) { // Listens in /register/:registration_id. // // This is not part of the Tailscale control API, as we could send whatever URL -// in the [tailcfg.RegisterResponse.AuthURL] field. +// in the RegisterResponse.AuthURL field. func (a *AuthProviderWeb) RegisterHandler( writer http.ResponseWriter, req *http.Request, diff --git a/hscontrol/handlers_test.go b/hscontrol/handlers_test.go index 9fe315c28..12ab7f96c 100644 --- a/hscontrol/handlers_test.go +++ b/hscontrol/handlers_test.go @@ -3,26 +3,20 @@ package hscontrol import ( "bytes" "context" - "encoding/json" "errors" "net/http" "net/http/httptest" - "net/netip" "strings" "testing" - "github.com/juanfont/headscale/hscontrol/types" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" - "tailscale.com/types/key" ) var errTestUnexpected = errors.New("unexpected failure") // TestHandleVerifyRequest_OversizedBodyRejected verifies that the -// /verify handler refuses POST bodies larger than [verifyBodyLimit]. -// The [http.MaxBytesReader] is applied in [Headscale.VerifyHandler], so we simulate +// /verify handler refuses POST bodies larger than verifyBodyLimit. +// The MaxBytesReader is applied in VerifyHandler, so we simulate // the same wrapping here. func TestHandleVerifyRequest_OversizedBodyRejected(t *testing.T) { t.Parallel() @@ -53,70 +47,7 @@ func TestHandleVerifyRequest_OversizedBodyRejected(t *testing.T) { "oversized body must surface 413") } -// TestVerifyHandler_SuccessSetsJSONContentType verifies that a successful -// POST to /verify advertises Content-Type: application/json. The header -// must be set before the JSON body is written, otherwise the implicit -// WriteHeader on first Write locks in a sniffed content type and the -// later Header().Set becomes a no-op. -func TestVerifyHandler_SuccessSetsJSONContentType(t *testing.T) { - tmpDir := t.TempDir() - - prefixV4 := netip.MustParsePrefix("100.64.0.0/10") - prefixV6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - cfg := &types.Config{ - ServerURL: "http://localhost:0", - NoisePrivateKeyPath: tmpDir + "/noise_private.key", - PrefixV4: &prefixV4, - PrefixV6: &prefixV6, - IPAllocation: types.IPAllocationStrategySequential, - Database: types.DatabaseConfig{ - Type: "sqlite3", - Sqlite: types.SqliteConfig{ - Path: tmpDir + "/headscale_test.db", - }, - }, - Policy: types.PolicyConfig{ - Mode: types.PolicyModeDB, - }, - } - - h, err := NewHeadscale(cfg) - require.NoError(t, err) - - reqBody, err := json.Marshal(tailcfg.DERPAdmitClientRequest{ - NodePublic: key.NewNode().Public(), - }) - require.NoError(t, err) - - // A real HTTP server is required to observe the bug: the first body - // Write triggers an implicit WriteHeader that snapshots the header - // map, so a Content-Type set afterwards never reaches the wire. - // An httptest.ResponseRecorder does not snapshot, so it would hide - // the defect. - srv := httptest.NewServer(http.HandlerFunc(h.VerifyHandler)) - defer srv.Close() - - httpReq, err := http.NewRequestWithContext( - context.Background(), - http.MethodPost, - srv.URL+"/verify", - bytes.NewReader(reqBody), - ) - require.NoError(t, err) - httpReq.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(httpReq) - require.NoError(t, err) - - defer resp.Body.Close() - - assert.Equal(t, http.StatusOK, resp.StatusCode) - assert.Equal(t, "application/json", resp.Header.Get("Content-Type"), - "successful /verify response must advertise application/json") -} - -// errorAsHTTPError is a small local helper that unwraps an [HTTPError] +// errorAsHTTPError is a small local helper that unwraps an HTTPError // from an error chain. func errorAsHTTPError(err error) (HTTPError, bool) { var h HTTPError diff --git a/hscontrol/mapper/batch_coalesce_test.go b/hscontrol/mapper/batch_coalesce_test.go deleted file mode 100644 index d89a48766..000000000 --- a/hscontrol/mapper/batch_coalesce_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package mapper - -import ( - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/types/change" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestProcessBatchedChangesCoalescesWhenInFlight verifies that at most one -// batched bundle per node is queued at a time. While a node's bundle is in -// flight, a new tick's changes must stay in pending (coalesced) rather than -// being queued as a second bundle that a non-FIFO worker could deliver out of -// order. -func TestProcessBatchedChangesCoalescesWhenInFlight(t *testing.T) { - b := NewBatcher(50*time.Millisecond, 2, nil) // not started: no ticker, no workers - - id := types.NodeID(1) - nc := newMultiChannelNodeConn(id, nil) - b.nodes.Store(id, nc) - - // A bundle is in flight: the new change must be retained, not queued. - nc.inFlight.Store(true) - nc.appendPending(change.PolicyChange()) - b.processBatchedChanges() - - nc.pendingMu.Lock() - retained := len(nc.pending) - nc.pendingMu.Unlock() - assert.Equal(t, 1, retained, "pending must be retained while a bundle is in flight") - - // No bundle in flight: the pending change is queued and the node marked - // in-flight. - nc.inFlight.Store(false) - b.processBatchedChanges() - - nc.pendingMu.Lock() - drained := len(nc.pending) - nc.pendingMu.Unlock() - assert.Equal(t, 0, drained, "pending must be drained once queued") - assert.True(t, nc.inFlight.Load(), "queued bundle must mark the node in-flight") - - require.NotNil(t, b) -} diff --git a/hscontrol/mapper/batcher.go b/hscontrol/mapper/batcher.go index 078ebd8b5..ce5697322 100644 --- a/hscontrol/mapper/batcher.go +++ b/hscontrol/mapper/batcher.go @@ -27,7 +27,7 @@ var ( ) // offlineNodeCleanupThreshold is how long a node must be disconnected -// before [Batcher.cleanupOfflineNodes] removes its in-memory state. +// before cleanupOfflineNodes removes its in-memory state. const offlineNodeCleanupThreshold = 15 * time.Minute var mapResponseGenerated = promauto.NewCounterVec(prometheus.CounterOpts{ @@ -49,7 +49,7 @@ func NewBatcher(batchTime time.Duration, workers int, mapper *mapper) *Batcher { } } -// NewBatcherAndMapper creates a new [Batcher] with its [mapper]. +// NewBatcherAndMapper creates a new Batcher with its mapper. func NewBatcherAndMapper(cfg *types.Config, state *state.State) *Batcher { m := newMapper(cfg, state) b := NewBatcher(cfg.Tuning.BatchChangeDelay, cfg.Tuning.BatcherWorkers, m) @@ -69,7 +69,7 @@ type nodeConnection interface { updateSentPeers(resp *tailcfg.MapResponse) } -// generateMapResponse generates a [tailcfg.MapResponse] for the given [types.NodeID] based on the provided [change.Change]. +// generateMapResponse generates a [tailcfg.MapResponse] for the given NodeID based on the provided [change.Change]. func generateMapResponse(nc nodeConnection, mapper *mapper, r change.Change) (*tailcfg.MapResponse, error) { nodeID := nc.nodeID() version := nc.version() @@ -130,19 +130,17 @@ func generateMapResponse(nc nodeConnection, mapper *mapper, r change.Change) (*t // When a full update (SendAllPeers=true) produces zero visible peers // (e.g., a restrictive policy isolates this node), the resulting - // [tailcfg.MapResponse] has Peers: []*tailcfg.Node{} (empty non-nil slice). + // MapResponse has Peers: []*tailcfg.Node{} (empty non-nil slice). // // The Tailscale client only treats Peers as a full authoritative // replacement when len(Peers) > 0 (controlclient/map.go:462). // An empty Peers slice is indistinguishable from a delta response, // so the client silently preserves its existing peer state. // - // This matters when a [change.FullUpdate] replaces a pending - // [change.PolicyChange] in the batcher ([Batcher.addToBatch] - // short-circuits on [change.HasFull]). The [change.PolicyChange] - // would have computed PeersRemoved via - // [multiChannelNodeConn.computePeerDiff], but the [change.FullUpdate] - // path uses [MapResponseBuilder.WithPeers] which sets Peers: []. + // This matters when a FullUpdate() replaces a pending PolicyChange() + // in the batcher (addToBatch short-circuits on HasFull). The + // PolicyChange would have computed PeersRemoved via computePeerDiff, + // but the FullUpdate path uses WithPeers which sets Peers: []. // // Fix: when a full update results in zero peers, compute the diff // against lastSentPeers and add explicit PeersRemoved entries so @@ -208,7 +206,7 @@ type workResult struct { // work represents a unit of work to be processed by workers. // All pending changes for a node are bundled into a single work item // so that one worker processes them sequentially. This prevents -// out-of-order [tailcfg.MapResponse] delivery and races on lastSentPeers +// out-of-order MapResponse delivery and races on lastSentPeers // that occur when multiple workers process changes for the same node. type work struct { changes []change.Change @@ -227,9 +225,9 @@ var ( // Batcher batches and distributes map responses to connected nodes. // It uses concurrent maps, per-node mutexes, and a worker pool. // -// Lifecycle: Call [Batcher.Start] to spawn workers, then [Batcher.Close] -// to shut down. [Batcher.Close] blocks until all workers have exited. -// A [Batcher] must not be reused after [Batcher.Close]. +// Lifecycle: Call Start() to spawn workers, then Close() to shut down. +// Close() blocks until all workers have exited. A Batcher must not +// be reused after Close(). type Batcher struct { tick *time.Ticker mapper *mapper @@ -280,42 +278,28 @@ func (b *Batcher) AddNode( created: now, stop: stop, } - // Block broadcast sends to this connection until its initial map - // is delivered below, so a delta cannot become the stream's first - // frame — clients reject streams whose first frame lacks the self - // node. - newEntry.pendingInitial.Store(true) // Initialize last used timestamp newEntry.lastUsed.Store(now.Unix()) - // Get or create the multiChannelNodeConn and register this connection in a - // single Compute so the new connection is visible atomically. Doing the - // LoadOrStore and addConnection as separate steps let cleanupOfflineNodes - // (which deletes via Compute when hasActiveConnections() is false) observe a - // reused offline conn with zero connections mid-reconnect and delete it, - // orphaning the live connection. - var nodeConn *multiChannelNodeConn + // Get or create multiChannelNodeConn - this reuses existing offline nodes for rapid reconnection + nodeConn, loaded := b.nodes.LoadOrStore(id, newMultiChannelNodeConn(id, b.mapper)) - b.nodes.Compute( - id, - func(existing *multiChannelNodeConn, loaded bool) (*multiChannelNodeConn, xsync.ComputeOp) { - if !loaded || existing == nil { - existing = newMultiChannelNodeConn(id, b.mapper) - b.totalNodes.Add(1) - } + if !loaded { + b.totalNodes.Add(1) + } - existing.addConnection(newEntry) - nodeConn = existing - - return existing, xsync.UpdateOp - }, - ) + // Add connection to the list (lock-free) + nodeConn.addConnection(newEntry) // Use the worker pool for controlled concurrency instead of direct generation initialMap, err := b.MapResponseFromChange(id, change.FullSelf(id)) if err != nil { nlog.Error().Err(err).Msg("initial map generation failed") - nodeConn.detach(c) + nodeConn.removeConnectionByChannel(c) + + if !nodeConn.hasActiveConnections() { + nodeConn.markDisconnected() + } return fmt.Errorf("generating initial map for node %d: %w", id, err) } @@ -324,22 +308,16 @@ func (b *Batcher) AddNode( // and we want to avoid the race condition where the receiver isn't ready yet select { case c <- initialMap: - // Record sent peers only after confirmed delivery, mirroring the async - // path, and under workMu so a concurrent async bundle for this node - // cannot interleave its own lastSentPeers update. - nodeConn.workMu.Lock() - nodeConn.updateSentPeers(initialMap) - nodeConn.workMu.Unlock() - - // Open the connection for broadcast sends now that the initial - // map is the stream's first frame; send() requeued any changes - // that arrived in the meantime. - newEntry.pendingInitial.Store(false) + // Success case <-time.After(5 * time.Second): //nolint:mnd nlog.Error().Err(ErrInitialMapSendTimeout).Msg("initial map send timeout") nlog.Debug().Caller().Dur("timeout.duration", 5*time.Second). //nolint:mnd Msg("initial map send timed out because channel was blocked or receiver not ready") - nodeConn.detach(c) + nodeConn.removeConnectionByChannel(c) + + if !nodeConn.hasActiveConnections() { + nodeConn.markDisconnected() + } return fmt.Errorf("%w for node %d", ErrInitialMapSendTimeout, id) } @@ -503,11 +481,9 @@ func (b *Batcher) worker(workerID int) { Uint64(zf.NodeID, w.nodeID.Uint64()). Str(zf.Reason, w.changes[0].Reason). Msg("failed to generate map response for synchronous work") + } else if result.mapResponse != nil { + nc.updateSentPeers(result.mapResponse) } - // Peer tracking is recorded by the caller (AddNode) only - // after the initial map is actually delivered; recording it - // here, before delivery, would leave phantom lastSentPeers - // if the send times out. nc.workMu.Unlock() } else { @@ -534,24 +510,9 @@ func (b *Batcher) worker(workerID int) { // finish — preventing out-of-order delivery and races // on lastSentPeers (Clear+Store vs Range). if nc, exists := b.nodes.Load(w.nodeID); exists && nc != nil { - // Changes that found only connections still awaiting - // their initial map are retried next tick: the - // in-flight initial map may have been generated from - // a snapshot older than the change, so dropping them - // would lose updates. Collected and prepended as a - // group to keep their order ahead of newer pending - // changes — order matters for stateful patches like - // online/offline. - var retry []change.Change - nc.workMu.Lock() for _, ch := range w.changes { err := nc.change(ch) - if errors.Is(err, errNoReadyConnections) { - retry = append(retry, ch) - continue - } - if err != nil { b.workErrors.Add(1) wlog.Error().Err(err). @@ -561,13 +522,6 @@ func (b *Batcher) worker(workerID int) { } } nc.workMu.Unlock() - - if len(retry) > 0 { - nc.prependPending(retry...) - } - - // Bundle delivered; allow the next tick's bundle to queue. - nc.inFlight.Store(false) } case <-b.done: wlog.Debug().Msg("batcher shutting down, exiting worker") @@ -597,11 +551,11 @@ func (b *Batcher) addToBatch(changes ...change.Change) { // still has it registered. By cleaning up here, we prevent "node not found" // errors when workers try to generate map responses for deleted nodes. // - // Safety: [change.Change.PeersRemoved] is ONLY populated when nodes are actually - // deleted from the system (via [change.NodeRemoved] in [state.State.DeleteNode]). - // Policy changes that affect peer visibility do NOT use this field - they set + // Safety: change.Change.PeersRemoved is ONLY populated when nodes are actually + // deleted from the system (via change.NodeRemoved in state.DeleteNode). Policy + // changes that affect peer visibility do NOT use this field - they set // RequiresRuntimePeerComputation=true and compute removed peers at runtime, - // putting them in [tailcfg.MapResponse.PeersRemoved] (a different struct). + // putting them in tailcfg.MapResponse.PeersRemoved (a different struct). // Therefore, this cleanup only removes nodes that are truly being deleted, // not nodes that are still connected but have lost visibility of certain peers. // @@ -669,24 +623,14 @@ func (b *Batcher) processBatchedChanges() { return true } - // Only one batched bundle per node may be in flight at a time. If the - // previous tick's bundle is still queued or processing, leave this - // tick's changes in pending; they are picked up once it completes. This - // keeps delivery ordered even when the worker pool is saturated. - if nc.inFlight.Load() { - return true - } - pending := nc.drainPending() if len(pending) == 0 { return true } - // One policy recompute rebuilds the whole netmap; drop same-tick repeats. - pending = change.DedupePolicyChanges(pending) - // Queue a single work item containing all pending changes. - nc.inFlight.Store(true) + // One item per node ensures a single worker processes them + // sequentially, preventing out-of-order delivery. b.queueWork(work{changes: pending, nodeID: nodeID, resultCh: nil}) return true @@ -694,8 +638,8 @@ func (b *Batcher) processBatchedChanges() { } // cleanupOfflineNodes removes nodes that have been offline for too long to prevent memory leaks. -// Uses xsync.Map.Compute for atomic check-and-delete to prevent TOCTOU races where a node -// reconnects between the hasActiveConnections check and the Delete call. +// Uses Compute() for atomic check-and-delete to prevent TOCTOU races where a node +// reconnects between the hasActiveConnections() check and the Delete() call. func (b *Batcher) cleanupOfflineNodes() { var nodesToCleanup []types.NodeID diff --git a/hscontrol/mapper/batcher_test.go b/hscontrol/mapper/batcher_test.go index 2e36d5aa0..c84c3d9f3 100644 --- a/hscontrol/mapper/batcher_test.go +++ b/hscontrol/mapper/batcher_test.go @@ -66,7 +66,7 @@ func (t *testBatcherWrapper) AddNode(id types.NodeID, c chan<- *tailcfg.MapRespo return fmt.Errorf("%w: %d", errNodeNotFoundAfterAdd, id) } - t.AddWork(change.NodeOnline(node.ID())) + t.AddWork(change.NodeOnlineFor(node)) return nil } @@ -90,7 +90,7 @@ func (t *testBatcherWrapper) RemoveNode(id types.NodeID, c chan<- *tailcfg.MapRe // Do this BEFORE removing from batcher so the change can be processed node, ok := t.state.GetNodeByID(id) if ok { - t.AddWork(change.NodeOffline(node.ID())) + t.AddWork(change.NodeOfflineFor(node)) } // Finally remove from the real batcher @@ -158,14 +158,14 @@ type node struct { // // Returns TestData struct containing all created entities and a cleanup function. func setupBatcherWithTestData( - tb testing.TB, + t testing.TB, bf batcherFunc, userCount, nodesPerUser, bufferSize int, ) (*TestData, func()) { - tb.Helper() + t.Helper() // Create database and populate with test data first - tmpDir := tb.TempDir() + tmpDir := t.TempDir() dbPath := tmpDir + "/headscale_test.db" prefixV4 := netip.MustParsePrefix("100.64.0.0/10") @@ -206,7 +206,7 @@ func setupBatcherWithTestData( // Create database and populate it with test data database, err := db.NewHeadscaleDatabase(cfg) if err != nil { - tb.Fatalf("setting up database: %s", err) + t.Fatalf("setting up database: %s", err) } // Create test users and nodes in the database @@ -226,12 +226,12 @@ func setupBatcherWithTestData( // Now create state using the same database state, err := state.NewState(cfg) if err != nil { - tb.Fatalf("Failed to create state: %v", err) + t.Fatalf("Failed to create state: %v", err) } derpMap, err := derp.GetDERPMap(cfg.DERP) - require.NoError(tb, err) - require.NotNil(tb, derpMap) + require.NoError(t, err) + require.NotNil(t, derpMap) state.SetDERPMap(derpMap) @@ -248,7 +248,7 @@ func setupBatcherWithTestData( _, err = state.SetPolicy([]byte(allowAllPolicy)) if err != nil { - tb.Fatalf("Failed to set allow-all policy: %v", err) + t.Fatalf("Failed to set allow-all policy: %v", err) } // Create batcher with the state and wrap it for testing @@ -1104,72 +1104,6 @@ func TestBatcherWorkQueueBatching(t *testing.T) { } } -// TestBatcherCoalescesPolicyRecomputesPerTick proves that many identical -// broadcast policy changes arriving in a single batcher tick collapse to one -// runtime peer recompute per node. Without coalescing, each PolicyChange drives -// a separate full netmap rebuild for every connected node (the reconnect-storm -// fan-out); with it, a node sees at most one recompute per tick. -func TestBatcherCoalescesPolicyRecomputesPerTick(t *testing.T) { - for _, bf := range allBatcherFunctions { - t.Run(bf.name, func(t *testing.T) { - const ( - nodesPerUser = 4 - policyChangesPerTick = 8 - ) - - testData, cleanup := setupBatcherWithTestData(t, bf.fn, 1, nodesPerUser, 100) - defer cleanup() - - batcher := testData.Batcher - for i := range testData.Nodes { - n := &testData.Nodes[i] - require.NoError(t, batcher.AddNode(n.n.ID, n.ch, tailcfg.CapabilityVersion(100), nil)) - } - - // Many identical broadcast policy changes, then a DERP-map change as - // a sentinel. All land in one tick; the sentinel rides the same work - // item after the recompute(s), so its arrival marks the end of this - // tick's policy responses for a node. - for range policyChangesPerTick { - batcher.AddWork(change.PolicyChange()) - } - - batcher.AddWork(change.DERPMap()) - - for i := range testData.Nodes { - id := testData.Nodes[i].n.ID - ch := testData.Nodes[i].ch - - policyResponses := 0 - deadline := time.After(2 * time.Second) - - drain: - for { - select { - case resp := <-ch: - switch { - case resp.DERPMap != nil && len(resp.Peers) == 0: - // Sentinel: every policy recompute for this tick has - // already been delivered to this node. - break drain - case len(resp.PacketFilters) > 0 && len(resp.Peers) == 0: - // A runtime peer recompute (policyChangeResponse): - // packet filters and incremental peers, no full list. - policyResponses++ - } - case <-deadline: - t.Fatalf("node %d never received the DERP sentinel", id) - } - } - - assert.LessOrEqualf(t, policyResponses, 1, - "node %d received %d policy recomputes in one tick; identical recomputes must coalesce to one", - id, policyResponses) - } - }) - } -} - // TestBatcherWorkerChannelSafety tests that worker goroutines handle closed // channels safely without panicking when processing work items. // diff --git a/hscontrol/mapper/builder.go b/hscontrol/mapper/builder.go index eaf4dde44..99d3185a3 100644 --- a/hscontrol/mapper/builder.go +++ b/hscontrol/mapper/builder.go @@ -1,22 +1,20 @@ package mapper import ( - "cmp" "net/netip" "slices" + "sort" "time" "github.com/juanfont/headscale/hscontrol/policy" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util/zlog/zf" - "github.com/rs/zerolog/log" "tailscale.com/tailcfg" "tailscale.com/types/views" "tailscale.com/util/multierr" ) -// MapResponseBuilder provides a fluent interface for building [tailcfg.MapResponse]. +// MapResponseBuilder provides a fluent interface for building tailcfg.MapResponse. type MapResponseBuilder struct { resp *tailcfg.MapResponse mapper *mapper @@ -63,16 +61,6 @@ func (b *MapResponseBuilder) hasErrors() bool { return len(b.errs) > 0 } -// node looks up the requesting node, recording ErrNodeNotFoundMapper on miss. -func (b *MapResponseBuilder) node() (types.NodeView, bool) { - nv, ok := b.mapper.state.GetNodeByID(b.nodeID) - if !ok { - b.addError(ErrNodeNotFoundMapper) - } - - return nv, ok -} - // WithCapabilityVersion sets the capability version for the response. func (b *MapResponseBuilder) WithCapabilityVersion(capVer tailcfg.CapabilityVersion) *MapResponseBuilder { b.capVer = capVer @@ -81,8 +69,9 @@ func (b *MapResponseBuilder) WithCapabilityVersion(capVer tailcfg.CapabilityVers // WithSelfNode adds the requesting node to the response. func (b *MapResponseBuilder) WithSelfNode() *MapResponseBuilder { - nv, ok := b.node() + nv, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { + b.addError(ErrNodeNotFoundMapper) return b } @@ -147,22 +136,15 @@ func (b *MapResponseBuilder) WithDebugConfig() *MapResponseBuilder { // WithSSHPolicy adds SSH policy configuration for the requesting node. func (b *MapResponseBuilder) WithSSHPolicy() *MapResponseBuilder { - node, ok := b.node() + node, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { + b.addError(ErrNodeNotFoundMapper) return b } sshPolicy, err := b.mapper.state.SSHPolicy(node) if err != nil { - // SSH policy is optional for a node to function. Rather than fail the - // whole map (leaving the node unable to connect), log and continue - // without it; the node still receives a usable netmap. - log.Warn().Caller(). - Err(err). - Uint64(zf.NodeID, node.ID().Uint64()). - Str(zf.NodeHostname, node.Hostname()). - Msg("building map response: skipping SSH policy for node; node will receive a map without SSH rules") - + b.addError(err) return b } @@ -173,8 +155,9 @@ func (b *MapResponseBuilder) WithSSHPolicy() *MapResponseBuilder { // WithDNSConfig adds DNS configuration for the requesting node. func (b *MapResponseBuilder) WithDNSConfig() *MapResponseBuilder { - node, ok := b.node() + node, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { + b.addError(ErrNodeNotFoundMapper) return b } @@ -185,8 +168,9 @@ func (b *MapResponseBuilder) WithDNSConfig() *MapResponseBuilder { // WithUserProfiles adds user profiles for the requesting node and given peers. func (b *MapResponseBuilder) WithUserProfiles(peers views.Slice[types.NodeView]) *MapResponseBuilder { - node, ok := b.node() + node, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { + b.addError(ErrNodeNotFoundMapper) return b } @@ -196,16 +180,16 @@ func (b *MapResponseBuilder) WithUserProfiles(peers views.Slice[types.NodeView]) } // WithPacketFilters adds packet filter rules based on policy. -// -// [State.FilterForNode] returns rules already reduced to only those relevant for this node. -// For autogroup:self policies, it returns per-node compiled rules. -// For global policies, it returns the global filter reduced for this node. func (b *MapResponseBuilder) WithPacketFilters() *MapResponseBuilder { - node, ok := b.node() + node, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { + b.addError(ErrNodeNotFoundMapper) return b } + // FilterForNode returns rules already reduced to only those relevant for this node. + // For autogroup:self policies, it returns per-node compiled rules. + // For global policies, it returns the global filter reduced for this node. filter, err := b.mapper.state.FilterForNode(node) if err != nil { b.addError(err) @@ -249,8 +233,7 @@ func (b *MapResponseBuilder) WithPeerChanges(peers views.Slice[types.NodeView]) return b } -// buildTailPeers converts [views.Slice] of [types.NodeView] to a slice of [tailcfg.Node] -// with policy filtering and sorting. +// buildTailPeers converts views.Slice[types.NodeView] to []tailcfg.Node with policy filtering and sorting. func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) ([]*tailcfg.Node, error) { node, ok := b.mapper.state.GetNodeByID(b.nodeID) if !ok { @@ -258,10 +241,9 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) ( } // Get unreduced matchers for peer relationship determination. - // [State.MatchersForNode] returns unreduced matchers that include all rules where the - // node could be either source or destination. This is different from - // [State.FilterForNode] which returns reduced rules for packet filtering (only rules - // where node is destination). + // MatchersForNode returns unreduced matchers that include all rules where the node + // could be either source or destination. This is different from FilterForNode which + // returns reduced rules for packet filtering (only rules where node is destination). matchers, err := b.mapper.state.MatchersForNode(node) if err != nil { return nil, err @@ -294,19 +276,7 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) ( return b.mapper.state.RoutesForPeer(node, peer, matchers) }, b.mapper.cfg, allCapMaps[peer.ID()]) if err != nil { - // One peer with invalid data (e.g. an empty or over-long - // GivenName that fails GetFQDN) must not blank out the map for - // every node that can see it. Drop the offending peer, log it - // with the identity an operator needs to fix it, and keep - // building from the remaining valid peers. - log.Warn().Caller(). - Err(err). - Uint64(zf.NodeID, peer.ID().Uint64()). - Str(zf.NodeHostname, peer.Hostname()). - Uint64("map.viewer.node.id", b.nodeID.Uint64()). - Msgf("dropping peer %d from map response: invalid node data; fix with `headscale nodes rename %d `", peer.ID(), peer.ID()) - - continue + return nil, err } // [tailcfg.Node.CapMap] on a peer carries the small set of @@ -323,8 +293,8 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) ( } // Peers is always returned sorted by Node.ID. - slices.SortStableFunc(tailPeers, func(a, b *tailcfg.Node) int { - return cmp.Compare(a.ID, b.ID) + sort.SliceStable(tailPeers, func(x, y int) bool { + return tailPeers[x].ID < tailPeers[y].ID }) return tailPeers, nil diff --git a/hscontrol/mapper/dns_extrarecords_race_test.go b/hscontrol/mapper/dns_extrarecords_race_test.go deleted file mode 100644 index 424f7fde1..000000000 --- a/hscontrol/mapper/dns_extrarecords_race_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package mapper - -import ( - "sync" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "tailscale.com/tailcfg" -) - -// TestExtraRecordsConcurrentUpdateNoRace exercises the extra-records watcher -// write path (Config.SetExtraRecords) concurrently with per-client DNS config -// builds (generateDNSConfig -> Config.CloneTailcfgDNSConfig). Both must go -// through the shared lock so the run is race-free under -race. -func TestExtraRecordsConcurrentUpdateNoRace(t *testing.T) { - uid := uint(1) - cfg := &types.Config{ - TailcfgDNSConfig: &tailcfg.DNSConfig{ - ExtraRecords: []tailcfg.DNSRecord{ - {Name: "initial.example.com", Type: "A", Value: "100.64.0.1"}, - }, - }, - } - - node := (&types.Node{ - Hostname: "race-node", - UserID: &uid, - User: &types.User{Name: "racer"}, - }).View() - - const iterations = 2000 - - var wg sync.WaitGroup - - // Writer: the extra-records update path. - wg.Go(func() { - for i := range iterations { - recs := []tailcfg.DNSRecord{{Name: "a.example.com", Type: "A", Value: "100.64.0.2"}} - if i%2 == 0 { - recs = append(recs, tailcfg.DNSRecord{Name: "b.example.com", Type: "A", Value: "100.64.0.3"}) - } - - cfg.SetExtraRecords(recs) - } - }) - - // Readers: the per-client map build path. - const readers = 8 - for range readers { - wg.Go(func() { - for range iterations { - if d := generateDNSConfig(cfg, node, nil); d != nil { - _ = len(d.ExtraRecords) - } - } - }) - } - - wg.Wait() -} diff --git a/hscontrol/mapper/initialmap_phantom_test.go b/hscontrol/mapper/initialmap_phantom_test.go deleted file mode 100644 index 362bd4f1a..000000000 --- a/hscontrol/mapper/initialmap_phantom_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package mapper - -import ( - "errors" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/types/change" - "tailscale.com/tailcfg" -) - -// TestUnreadyConnectionDefersBroadcastsUntilInitialMap pins the ordering fix -// for the "initial MapResponse lacked Node" client failure: a connection -// registered by [Batcher.AddNode] but still waiting for its initial map must -// not receive broadcast deltas — a delta as the stream's first frame makes the -// Tailscale client tear down the poll. The change is requeued, not dropped, -// because the in-flight initial map may have been generated from a snapshot -// older than the change. -func TestUnreadyConnectionDefersBroadcastsUntilInitialMap(t *testing.T) { - testData, cleanup := setupBatcherWithTestData(t, NewBatcherAndMapper, 1, 2, normalBufferSize) - defer cleanup() - - batcher := testData.Batcher.Batcher - peerNode := &testData.Nodes[0] - targetNode := &testData.Nodes[1] - - // Register the target's connection by hand in the state AddNode leaves it - // in between registering the channel and delivering the initial map. - nc := newMultiChannelNodeConn(targetNode.n.ID, batcher.mapper) - entry := &connectionEntry{ - id: "test-unready", - c: targetNode.ch, - version: tailcfg.CapabilityVersion(100), - created: time.Now(), - } - entry.pendingInitial.Store(true) - nc.addConnection(entry) - batcher.nodes.Store(targetNode.n.ID, nc) - - // A broadcast lands while the initial map is still in flight: nothing may - // reach the channel, and the caller must be told to retry - // (the worker prepends such changes back onto pending). - retryChange := change.NodeAdded(peerNode.n.ID) - - err := nc.change(retryChange) - if !errors.Is(err, errNoReadyConnections) { - t.Fatalf("change on unready connection: want errNoReadyConnections, got %v", err) - } - - select { - case resp := <-targetNode.ch: - t.Fatalf("unready connection received a frame before its initial map: %+v", resp) - default: - } - - // Once the initial map is delivered, the retried change goes out. - entry.pendingInitial.Store(false) - - err = nc.change(retryChange) - if err != nil { - t.Fatalf("change on ready connection: %v", err) - } - - select { - case resp := <-targetNode.ch: - if len(resp.PeersChanged) == 0 { - t.Fatalf("expected PeersChanged delta after readiness, got %+v", resp) - } - default: - t.Fatal("ready connection did not receive the retried change") - } -} - -// TestSyncInitialMapNoPhantomPeersOnTimeout ensures the synchronous initial-map -// path does not record peers as sent until the map is actually delivered. When -// the AddNode channel send times out, the client received nothing, so -// lastSentPeers must stay empty; otherwise future computePeerDiff calculations -// miss peer additions or removals after reconnect. -func TestSyncInitialMapNoPhantomPeersOnTimeout(t *testing.T) { - testData, cleanup := setupBatcherWithTestData(t, NewBatcherAndMapper, 1, 2, normalBufferSize) - defer cleanup() - - batcher := testData.Batcher.Batcher - state := testData.State - - peerNode := &testData.Nodes[0] - targetNode := &testData.Nodes[1] - - state.Connect(peerNode.n.ID) - - err := batcher.AddNode(peerNode.n.ID, peerNode.ch, tailcfg.CapabilityVersion(100), nil) - if err != nil { - t.Fatalf("adding peer node: %v", err) - } - - go func() { - for range peerNode.ch { - } - }() - - state.Connect(targetNode.n.ID) - - // Unbuffered channel that nobody reads: AddNode blocks on the initial-map - // send and times out. - unreadCh := make(chan *tailcfg.MapResponse) - - err = batcher.AddNode(targetNode.n.ID, unreadCh, tailcfg.CapabilityVersion(100), nil) - if err == nil { - t.Fatal("expected initial-map send timeout error, got nil") - } - - nc, exists := batcher.nodes.Load(targetNode.n.ID) - if !exists || nc == nil { - t.Fatalf("expected node %d to be retained in b.nodes", targetNode.n.ID) - } - - if nc.hasActiveConnections() { - t.Fatalf("expected node %d to have no active connections after timeout", targetNode.n.ID) - } - - var phantom []tailcfg.NodeID - - nc.lastSentPeers.Range(func(id tailcfg.NodeID, _ struct{}) bool { - phantom = append(phantom, id) - return true - }) - - if len(phantom) != 0 { - t.Errorf("lastSentPeers must be empty after a failed initial-map delivery, got %d: %v", - len(phantom), phantom) - } -} diff --git a/hscontrol/mapper/initialmap_storm_test.go b/hscontrol/mapper/initialmap_storm_test.go deleted file mode 100644 index 877a8d97b..000000000 --- a/hscontrol/mapper/initialmap_storm_test.go +++ /dev/null @@ -1,189 +0,0 @@ -//go:build !race - -// This is a timing-sensitive performance regression test; the race detector's -// ~10x slowdown makes its wall-clock assertion meaningless, so it is excluded -// from -race builds. The concurrency correctness of the policy lock change it -// guards is covered under -race by TestPolicyManagerConcurrentReads in -// hscontrol/policy/v2. - -package mapper - -import ( - "net/netip" - "runtime" - "slices" - "sync" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/derp" - "github.com/juanfont/headscale/hscontrol/state" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// setupStormBatcher builds a real state+batcher with production-default -// NodeStore batching so the reconnect-storm contention is realistic. It mirrors -// setupBatcherWithTestData but lets the test control BatcherWorkers and the -// policy. -func setupStormBatcher(tb testing.TB, nodeCount, workers int, policy string) (*TestData, func()) { - tb.Helper() - - tmpDir := tb.TempDir() - prefixV4 := netip.MustParsePrefix("100.64.0.0/10") - prefixV6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - cfg := &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{Path: tmpDir + "/headscale_test.db"}, - }, - PrefixV4: &prefixV4, - PrefixV6: &prefixV6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{Mode: types.PolicyModeDB}, - DERP: types.DERPConfig{ - ServerEnabled: false, - DERPMap: &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{999: {RegionID: 999}}, - }, - }, - Tuning: types.Tuning{ - BatchChangeDelay: 10 * time.Millisecond, - BatcherWorkers: workers, - // Production defaults: coalesce writes so the storm is not - // exaggerated by an unrealistically small NodeStore batch. - NodeStoreBatchSize: 100, - NodeStoreBatchTimeout: 500 * time.Millisecond, - }, - } - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(tb, err) - - users := database.CreateUsersForTest(1, "testuser") - dbNodes := database.CreateRegisteredNodesForTest(users[0], nodeCount, "node") - - allNodes := make([]node, 0, nodeCount) - for i := range dbNodes { - allNodes = append(allNodes, node{ - n: dbNodes[i], - ch: make(chan *tailcfg.MapResponse, normalBufferSize), - }) - } - - st, err := state.NewState(cfg) - require.NoError(tb, err) - - derpMap, err := derp.GetDERPMap(cfg.DERP) - require.NoError(tb, err) - st.SetDERPMap(derpMap) - - _, err = st.SetPolicy([]byte(policy)) - require.NoError(tb, err) - - batcher := wrapBatcherForTest(NewBatcherAndMapper(cfg, st), st) - batcher.Start() - - td := &TestData{ - Database: database, - Users: users, - Nodes: allNodes, - State: st, - Config: cfg, - Batcher: batcher, - } - - return td, func() { - batcher.Close() - st.Close() - database.Close() - } -} - -// TestInitialMapNotStarvedByReconnectStorm reproduces juanfont/headscale#3346. -// -// When every node redials at once (e.g. after a server upgrade restart), each -// connection writes the NodeStore (UpdateNodeFromMapRequest + Connect) and the -// batcher generates its initial map. All of that reads the policy through the -// PolicyManager. Before the fix the PolicyManager guarded every read with a -// single exclusive mutex, so the NodeStore writer's O(n^2) BuildPeerMap and -// every node's FilterForNode serialised against each other. On a per-node -// filter policy (autogroup:self, via, relay grants) each hold is expensive, so -// under the storm time-to-initial-map grew without bound. -// -// On the production server in #3346 this drove the batcher's per-node -// total.duration from ~4s to ~76s; tailscale clients aborted the map POST -// first and reported -// -// PollNetMap: Post ".../machine/map": unexpected EOF -// -// then redialled, feeding the storm so it never converged. An allow-all policy -// does NOT reproduce this — BuildPeerMap is cheap there; the per-node filter -// path is what makes it expensive, matching a real deployment's ACLs. -// -// The fix makes PolicyManager reads take a shared RLock so map generation runs -// concurrently. AddNode blocks until the initial map is generated and handed to -// the node channel, so its wall-clock duration is the time-to-initial-map the -// client experiences. Without the fix this test's slowest node takes ~10s+ at -// this scale (lock-bound, and more workers do not help); with it, generation -// parallelises across workers and stays well within a client's patience. -func TestInitialMapNotStarvedByReconnectStorm(t *testing.T) { - if testing.Short() { - t.Skip("timing-sensitive storm regression; skipped in -short") - } - - const ( - nodeCount = 300 - - // A per-node-filter policy: forces BuildPeerMap and FilterForNode onto - // the slow path that recompiles filter rules per node, the same shape - // as a real ACL using autogroup:self / via / relay grants. - perNodeFilterPolicy = `{"acls":[{"action":"accept","src":["autogroup:member"],"dst":["autogroup:self:*"]}]}` - - // Deliberately roomy so it passes on CI's few-core runners, where the - // single-writer BuildPeerMap sets the floor (~10s) whatever the reads - // do. It still trips on a hang or a return to the ~76s serialised - // behaviour; the fine-grained concurrency is verified separately by - // TestPolicyManagerConcurrentReads under -race. - maxAcceptableLatency = 30 * time.Second - ) - - // Use the real available parallelism, as production does. - workers := runtime.NumCPU() - - td, cleanup := setupStormBatcher(t, nodeCount, workers, perNodeFilterPolicy) - defer cleanup() - - latencies := make([]time.Duration, nodeCount) - - var wg sync.WaitGroup - - for i := range td.Nodes { - wg.Go(func() { - n := &td.Nodes[i] - - start := time.Now() - err := td.Batcher.AddNode(n.n.ID, n.ch, tailcfg.CapabilityVersion(100), nil) - latencies[i] = time.Since(start) - - assert.NoError(t, err) //nolint:testifylint // assert (not require) is correct off the test goroutine - }) - } - - wg.Wait() - - slices.Sort(latencies) - p50 := latencies[len(latencies)/2] - p95 := latencies[len(latencies)*95/100] - maxLatency := latencies[len(latencies)-1] - t.Logf("initial-map latency over %d nodes (workers=%d): p50=%s p95=%s max=%s", - nodeCount, workers, p50, p95, maxLatency) - - require.Less(t, maxLatency, maxAcceptableLatency, - "slowest initial map took %s: policy reads are serialising instead of running concurrently (issue #3346)", maxLatency) -} diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index bd04d2e73..b90d45a88 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/fs" - "maps" "net/url" "os" "path" @@ -14,7 +13,6 @@ import ( "strings" "time" - "github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/state" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types/change" @@ -52,6 +50,12 @@ type mapper struct { created time.Time } +//nolint:unused +type patch struct { + timestamp time.Time + change *tailcfg.PeerChange +} + func newMapper( cfg *types.Config, state *state.State, @@ -65,12 +69,13 @@ func newMapper( } } -// generateUserProfiles creates user profiles for [tailcfg.MapResponse]. +// generateUserProfiles creates user profiles for MapResponse. func generateUserProfiles( node types.NodeView, peers views.Slice[types.NodeView], ) []tailcfg.UserProfile { userMap := make(map[uint]*types.UserView) + ids := make([]uint, 0, len(userMap)) user := node.Owner() if !user.Valid() { @@ -81,7 +86,9 @@ func generateUserProfiles( return nil } - userMap[user.Model().ID] = &user + userID := user.Model().ID + userMap[userID] = &user + ids = append(ids, userID) for _, peer := range peers.All() { peerUser := peer.Owner() @@ -89,13 +96,20 @@ func generateUserProfiles( continue } - userMap[peerUser.Model().ID] = &peerUser + peerUserID := peerUser.Model().ID + userMap[peerUserID] = &peerUser + ids = append(ids, peerUserID) } + slices.Sort(ids) + ids = slices.Compact(ids) + var profiles []tailcfg.UserProfile - for _, id := range slices.Sorted(maps.Keys(userMap)) { - profiles = append(profiles, userMap[id].TailscaleUserProfile()) + for _, id := range ids { + if userMap[id] != nil { + profiles = append(profiles, userMap[id].TailscaleUserProfile()) + } } return profiles @@ -121,11 +135,12 @@ func generateDNSConfig( node types.NodeView, capMap tailcfg.NodeCapMap, ) *tailcfg.DNSConfig { - dnsConfig := cfg.CloneTailcfgDNSConfig() - if dnsConfig == nil { + if cfg.TailcfgDNSConfig == nil { return nil } + dnsConfig := cfg.TailcfgDNSConfig.Clone() + profile := nextDNSProfileFromCapMap(capMap) if profile != "" { applyNextDNSProfile(dnsConfig.Resolvers, profile) @@ -241,13 +256,7 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node types.NodeView) { q := u.Query() q.Set("device_name", node.Hostname()) - - // Guard Hostinfo().Valid() before dereferencing OS(): a node loaded - // from a legacy NULL host_info row has a nil Hostinfo, and OS() would - // panic. Mirrors the .Valid() guard in RequestTags/TailNode. - if node.Hostinfo().Valid() { - q.Set("device_model", node.Hostinfo().OS()) - } + q.Set("device_model", node.Hostinfo().OS()) if ips := node.IPs(); len(ips) > 0 { q.Set("device_ip", ips[0].String()) @@ -258,7 +267,7 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node types.NodeView) { } } -// fullMapResponse returns a [tailcfg.MapResponse] for the given node. +// fullMapResponse returns a MapResponse for the given node. // //nolint:unused func (m *mapper) fullMapResponse( @@ -303,20 +312,13 @@ func (m *mapper) selfMapResponse( return ma, err } -// policyChangeResponse creates a [tailcfg.MapResponse] for policy changes. +// policyChangeResponse creates a MapResponse for policy changes. // It sends: -// - PeersRemoved for peers that are no longer visible after the policy change -// - PeersChanged for remaining peers (their AllowedIPs may have changed due to policy) -// - Updated PacketFilters -// - Updated SSHPolicy (SSH rules may reference users/groups that changed) -// - DNSConfig so the client's resolver state stays anchored even when a -// policy-triggered wgengine reconfigure races a netmon LinkChange (the -// LinkChange handler reapplies dns.Manager.Set with the engine's -// lastDNSConfig; if that snapshot is stale, the OS resolver loses the -// MagicDNS reverse-DNS routes and Nameservers and curl-by-FQDN stops -// resolving for the rest of the policy window). -// - Optionally, the node's own self info (when includeSelf is true) -// +// - PeersRemoved for peers that are no longer visible after the policy change +// - PeersChanged for remaining peers (their AllowedIPs may have changed due to policy) +// - Updated PacketFilters +// - Updated SSHPolicy (SSH rules may reference users/groups that changed) +// - Optionally, the node's own self info (when includeSelf is true) // This avoids the issue where an empty Peers slice is interpreted by Tailscale // clients as "no change" rather than "no peers". // When includeSelf is true, the node's self info is included so that a node @@ -332,7 +334,6 @@ func (m *mapper) policyChangeResponse( builder := m.NewMapResponseBuilder(nodeID). WithDebugType(policyResponseDebug). WithCapabilityVersion(capVer). - WithDNSConfig(). WithPacketFilters(). WithSSHPolicy() @@ -341,7 +342,7 @@ func (m *mapper) policyChangeResponse( } if len(removedPeers) > 0 { - // Convert [tailcfg.NodeID] to [types.NodeID] for [MapResponseBuilder.WithPeersRemoved] + // Convert tailcfg.NodeID to types.NodeID for WithPeersRemoved removedIDs := make([]types.NodeID, len(removedPeers)) for i, id := range removedPeers { removedIDs[i] = types.NodeID(id) //nolint:gosec // NodeID types are equivalent @@ -362,7 +363,7 @@ func (m *mapper) policyChangeResponse( return builder.Build() } -// buildFromChange builds a [tailcfg.MapResponse] from a [change.Change] specification. +// buildFromChange builds a MapResponse from a change.Change specification. // This provides fine-grained control over what gets included in the response. func (m *mapper) buildFromChange( nodeID types.NodeID, @@ -411,7 +412,7 @@ func (m *mapper) buildFromChange( } else { if len(resp.PeersChanged) > 0 { peers := m.state.ListPeers(nodeID, resp.PeersChanged...) - builder.WithUserProfiles(m.filterVisibleNodes(nodeID, peers)) + builder.WithUserProfiles(peers) builder.WithPeerChanges(peers) } @@ -420,9 +421,8 @@ func (m *mapper) buildFromChange( } } - patches := m.filterVisiblePeerPatches(nodeID, resp.PeerPatches) - if len(patches) > 0 { - builder.WithPeerChangedPatch(patches) + if len(resp.PeerPatches) > 0 { + builder.WithPeerChangedPatch(resp.PeerPatches) } if resp.PingRequest != nil { @@ -432,106 +432,6 @@ func (m *mapper) buildFromChange( return builder.Build() } -// visiblePeerIDs returns the set of peer node IDs the recipient may see under -// the current policy. It is the single visibility decision shared by the -// incremental peer-change and user-profile paths, computed from the same live -// per-node matchers and [policy.ReduceNodes] filter that -// [MapResponseBuilder.buildTailPeers] applies to full peer objects, so the -// paths cannot drift. The snapshot peer map ([NodeStore.ListPeers]) is used -// only as the candidate set, matching buildTailPeers; the live policy decides -// visibility because the snapshot is not rebuilt on policy changes. -// -// ok is false when the node or its matchers cannot be resolved; callers must -// then fail closed (emit nothing) rather than risk leaking forbidden peers. -func (m *mapper) visiblePeerIDs(nodeID types.NodeID) (map[tailcfg.NodeID]struct{}, bool) { - node, ok := m.state.GetNodeByID(nodeID) - if !ok { - return nil, false - } - - matchers, err := m.state.MatchersForNode(node) - if err != nil { - return nil, false - } - - peers := m.state.ListPeers(nodeID) - - // No matchers means no policy restrictions, so every peer is visible — - // the same default buildTailPeers applies. - if len(matchers) > 0 { - peers = policy.ReduceNodes(node, peers, matchers) - } - - // Key by tailcfg.NodeID so the peer-patch path can look up by patch.NodeID - // directly, avoiding an unchecked int64->uint64 conversion. - visible := make(map[tailcfg.NodeID]struct{}, peers.Len()) - for _, peer := range peers.All() { - visible[peer.ID().NodeID()] = struct{}{} - } - - return visible, true -} - -// filterVisiblePeerPatches drops peer-change patches whose target peer the -// recipient cannot see under the ACL policy. Without it, online/offline, -// endpoint, and key-expiry patches disclose the existence, presence, and -// addresses of peers the recipient's policy forbids it from accessing. -func (m *mapper) filterVisiblePeerPatches( - nodeID types.NodeID, - patches []*tailcfg.PeerChange, -) []*tailcfg.PeerChange { - if len(patches) == 0 { - return patches - } - - visible, ok := m.visiblePeerIDs(nodeID) - if !ok { - // Fail closed: if visibility cannot be resolved, send no patches. - return nil - } - - return filterByVisible(visible, patches, func(p *tailcfg.PeerChange) tailcfg.NodeID { - return p.NodeID - }) -} - -// filterVisibleNodes restricts a peer slice to the nodes the recipient can see -// under the ACL policy. It guards UserProfiles on the incremental PeersChanged -// path, which receives an unfiltered node slice and would otherwise leak the -// identities of users whose nodes the recipient cannot access. -func (m *mapper) filterVisibleNodes( - nodeID types.NodeID, - peers views.Slice[types.NodeView], -) views.Slice[types.NodeView] { - visible, ok := m.visiblePeerIDs(nodeID) - if !ok { - // Fail closed: emit no peer user profiles rather than risk a leak. - return views.SliceOf([]types.NodeView{}) - } - - return views.SliceOf(filterByVisible(visible, peers.AsSlice(), func(p types.NodeView) tailcfg.NodeID { - return p.ID().NodeID() - })) -} - -// filterByVisible keeps only the items whose key resolves to a NodeID present -// in the visible set, preserving input order. -func filterByVisible[T any]( - visible map[tailcfg.NodeID]struct{}, - items []T, - key func(T) tailcfg.NodeID, -) []T { - var filtered []T - - for _, it := range items { - if _, ok := visible[key(it)]; ok { - filtered = append(filtered, it) - } - } - - return filtered -} - func writeDebugMapResponse( resp *tailcfg.MapResponse, t debugType, diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index 9b19c9498..54975e844 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -3,17 +3,11 @@ package mapper import ( "fmt" "net/netip" - "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/state" "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/types/change" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "tailscale.com/tailcfg" "tailscale.com/types/dnstype" ) @@ -199,452 +193,3 @@ func TestNextDNSCapMapRendering(t *testing.T) { } }) } - -// TestBuildFromChangeFiltersPeerPatchesByVisibility proves that incremental -// peer-change patches (online/offline, endpoint, key-expiry) are restricted to -// the recipient's ACL-visible peer set, the same way buildTailPeers filters -// full peer objects via policy.ReduceNodes. Without it, a node receives the -// existence, presence, and addresses of peers its policy forbids accessing. -func TestBuildFromChangeFiltersPeerPatchesByVisibility(t *testing.T) { - tmp := t.TempDir() - - p4 := netip.MustParsePrefix("100.64.0.0/10") - p6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - cfg := &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{Path: tmp + "/h.db"}, - }, - PrefixV4: &p4, - PrefixV6: &p6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{Mode: types.PolicyModeDB}, - DERP: types.DERPConfig{ - DERPMap: &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{999: {RegionID: 999}}, - }, - }, - Tuning: types.Tuning{ - NodeStoreBatchSize: state.TestBatchSize, - NodeStoreBatchTimeout: state.TestBatchTimeout, - }, - } - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user1 := database.CreateUserForTest("u1") - user2 := database.CreateUserForTest("u2") - n1 := database.CreateRegisteredNodeForTest(user1, "n1") - n1b := database.CreateRegisteredNodeForTest(user1, "n1b") - n2 := database.CreateRegisteredNodeForTest(user2, "n2") - require.NoError(t, database.Close()) - - s, err := state.NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Each user may reach only its own devices, so n1 cannot access n2. - policy := `{"acls":[ - {"action":"accept","src":["u1@"],"dst":["u1@:*"]}, - {"action":"accept","src":["u2@"],"dst":["u2@:*"]} - ]}` - _, err = s.SetPolicy([]byte(policy)) - require.NoError(t, err) - - m := &mapper{state: s, cfg: cfg} - - // n2 (user2) comes online; n1 (user1) must NOT receive its patch. - leakChange := change.NodeOnline(n2.ID) - resp, err := m.buildFromChange(n1.ID, tailcfg.CurrentCapabilityVersion, &leakChange) - require.NoError(t, err) - require.NotNil(t, resp) - - for _, p := range resp.PeersChangedPatch { - assert.NotEqual(t, n2.ID.NodeID(), p.NodeID, - "n1 must not receive an online patch for n2, which its policy forbids accessing") - } - - // Control: n1b (same user) coming online IS visible to n1. - okChange := change.NodeOnline(n1b.ID) - resp2, err := m.buildFromChange(n1.ID, tailcfg.CurrentCapabilityVersion, &okChange) - require.NoError(t, err) - require.NotNil(t, resp2) - - var gotVisible bool - - for _, p := range resp2.PeersChangedPatch { - if p.NodeID == n1b.ID.NodeID() { - gotVisible = true - } - } - - assert.True(t, gotVisible, - "n1 must receive the online patch for visible same-user peer n1b") -} - -// TestBuildFromChangeFiltersUserProfilesByVisibility proves the incremental -// PeersChanged path restricts UserProfiles to the recipient's ACL-visible -// peers, like the full-map path (whose ListPeers returns the -// BuildPeerMap-filtered set). Without it, a changed node broadcast to all -// nodes leaks its owner's identity (login name, display name, avatar) to -// recipients whose policy forbids accessing that node. -func TestBuildFromChangeFiltersUserProfilesByVisibility(t *testing.T) { - tmp := t.TempDir() - - p4 := netip.MustParsePrefix("100.64.0.0/10") - p6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - cfg := &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{Path: tmp + "/h.db"}, - }, - PrefixV4: &p4, - PrefixV6: &p6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{Mode: types.PolicyModeDB}, - DERP: types.DERPConfig{ - DERPMap: &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{999: {RegionID: 999}}, - }, - }, - Tuning: types.Tuning{ - NodeStoreBatchSize: state.TestBatchSize, - NodeStoreBatchTimeout: state.TestBatchTimeout, - }, - } - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user1 := database.CreateUserForTest("u1") - user2 := database.CreateUserForTest("u2") - n1 := database.CreateRegisteredNodeForTest(user1, "n1") - n2 := database.CreateRegisteredNodeForTest(user2, "n2") - require.NoError(t, database.Close()) - - s, err := state.NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Each user may reach only its own devices, so n1 cannot access n2. - policy := `{"acls":[ - {"action":"accept","src":["u1@"],"dst":["u1@:*"]}, - {"action":"accept","src":["u2@"],"dst":["u2@:*"]} - ]}` - _, err = s.SetPolicy([]byte(policy)) - require.NoError(t, err) - - m := &mapper{state: s, cfg: cfg} - - // n2 (user2) is added and broadcast. n1 (user1) cannot access it, so n1 - // must NOT receive user2's profile. - c := change.NodeAdded(n2.ID) - resp, err := m.buildFromChange(n1.ID, tailcfg.CurrentCapabilityVersion, &c) - require.NoError(t, err) - require.NotNil(t, resp) - - for _, up := range resp.UserProfiles { - assert.NotEqual(t, user2.TailscaleUserProfile().ID, up.ID, - "n1 must not receive user2's profile; n2 is not ACL-visible to n1") - } -} - -// TestBuildFromChangeVisibilityMatchesFullMap is the consolidation guard for -// PR #3304: the incremental change paths (peer patches via NodeOnline, changed -// peers via NodeAdded) must expose exactly the same ACL-visible peer set as the -// full-map path under every policy shape, and a cross-user UserProfile must not -// leak. If a future refactor lets one path drift from another, this fails. -// -// It pins two behaviours the scattered per-path filters get wrong today and the -// consolidation onto the snapshot peer map must fix: deny-all (empty matchers) -// must hide every peer on the incremental path rather than fall open to "no -// matchers => all visible", and per-node policies (autogroup:self) must agree -// across paths. -func TestBuildFromChangeVisibilityMatchesFullMap(t *testing.T) { - tmp := t.TempDir() - p4 := netip.MustParsePrefix("100.64.0.0/10") - p6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - cfg := &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{Path: tmp + "/h.db"}, - }, - PrefixV4: &p4, - PrefixV6: &p6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{Mode: types.PolicyModeDB}, - DERP: types.DERPConfig{ - DERPMap: &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{999: {RegionID: 999}}, - }, - }, - Tuning: types.Tuning{ - NodeStoreBatchSize: state.TestBatchSize, - NodeStoreBatchTimeout: state.TestBatchTimeout, - }, - } - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user1 := database.CreateUserForTest("u1") - user2 := database.CreateUserForTest("u2") - n1 := database.CreateRegisteredNodeForTest(user1, "n1") - n1b := database.CreateRegisteredNodeForTest(user1, "n1b") - n2 := database.CreateRegisteredNodeForTest(user2, "n2") - require.NoError(t, database.Close()) - - s, err := state.NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - m := &mapper{state: s, cfg: cfg} - capVer := tailcfg.CurrentCapabilityVersion - - // fullVisible returns the peer IDs n1 sees in the full map. - fullVisible := func(t *testing.T) map[tailcfg.NodeID]bool { - t.Helper() - - resp, err := m.fullMapResponse(n1.ID, capVer) - require.NoError(t, err) - - got := map[tailcfg.NodeID]bool{} - for _, p := range resp.Peers { - got[p.ID] = true - } - - return got - } - // patchReaches reports whether a NodeOnline patch for id is delivered to n1. - patchReaches := func(t *testing.T, id types.NodeID) bool { - t.Helper() - - c := change.NodeOnline(id) - resp, err := m.buildFromChange(n1.ID, capVer, &c) - require.NoError(t, err) - - if resp == nil { - return false - } - - for _, p := range resp.PeersChangedPatch { - if p.NodeID == id.NodeID() { - return true - } - } - - return false - } - // changedReaches reports whether a NodeAdded changed-peer for id reaches n1. - changedReaches := func(t *testing.T, id types.NodeID) bool { - t.Helper() - - c := change.NodeAdded(id) - resp, err := m.buildFromChange(n1.ID, capVer, &c) - require.NoError(t, err) - - if resp == nil { - return false - } - - for _, p := range resp.PeersChanged { - if p.ID == id.NodeID() { - return true - } - } - - return false - } - // profileReaches reports whether want's profile is delivered to n1 when n - // is added. Use a cross-user node so the result is not masked by n1's own - // always-present user profile. - profileReaches := func(t *testing.T, n *types.Node, want tailcfg.UserID) bool { - t.Helper() - - c := change.NodeAdded(n.ID) - resp, err := m.buildFromChange(n1.ID, capVer, &c) - require.NoError(t, err) - - if resp == nil { - return false - } - - for _, up := range resp.UserProfiles { - if up.ID == want { - return true - } - } - - return false - } - - // wantFull pins the actual peer-visibility semantics so the invariant below - // cannot pass vacuously (e.g. if every path broke to zero identically). - // Note deny_all: an empty ACL set compiles to zero matchers, which headscale - // treats as "no visibility restriction" — all peers are visible on every - // path (the packet filter denies traffic separately). user_isolation and - // autogroup_self are the discriminating cases that prove filtering works. - tests := []struct { - name string - policy string - wantFull int - }{ - {"allow_all", `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}`, 2}, - { - "user_isolation", - `{"acls":[ - {"action":"accept","src":["u1@"],"dst":["u1@:*"]}, - {"action":"accept","src":["u2@"],"dst":["u2@:*"]} - ]}`, - 1, - }, - {"deny_all", `{"acls":[]}`, 2}, - { - "autogroup_self", - `{"acls":[{"action":"accept","src":["autogroup:member"],"dst":["autogroup:self:*"]}]}`, - 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := s.SetPolicy([]byte(tt.policy)) - require.NoError(t, err) - - full := fullVisible(t) - require.Lenf(t, full, tt.wantFull, - "%s: unexpected full-map visible peer count", tt.name) - - for _, peer := range []*types.Node{n1b, n2} { - want := full[peer.ID.NodeID()] - assert.Equalf(t, want, patchReaches(t, peer.ID), - "%s: NodeOnline patch for %s must reach n1 iff full-map shows it", - tt.name, peer.Hostname) - assert.Equalf(t, want, changedReaches(t, peer.ID), - "%s: NodeAdded changed-peer for %s must reach n1 iff full-map shows it", - tt.name, peer.Hostname) - } - // Cross-user profile (user2) must appear iff n2 is visible to n1. - assert.Equalf(t, full[n2.ID.NodeID()], profileReaches(t, n2, user2.TailscaleUserProfile().ID), - "%s: user2 profile must be sent iff n2 is visible to n1", tt.name) - }) - } -} - -// TestFullMapResponseSurvivesPeerWithInvalidName proves a single node with an -// FQDN-invalid GivenName must not break map generation for its peers. -// -// A node whose stored GivenName is empty (ErrNodeHasNoGivenName) or yields an -// FQDN longer than MaxHostnameLength (ErrHostnameTooLong) makes GetFQDN, and -// therefore TailNode, return an error. buildTailPeers used to abort the entire -// peer list on the first such error, so MapResponseBuilder.Build() failed for -// every node that could see the bad peer; on the initial-connection path that -// surfaced as "PollNetMap: ... unexpected EOF" and the "Unable to connect to -// the Tailscale coordination server" health warning. A legacy DB row loads -// verbatim (NewNodeStore reads db.ListNodes() without re-sanitising names), so -// the bad peer persists across restart. The build for an unaffected viewer -// must succeed: the bad peer is dropped, valid peers and self survive. -func TestFullMapResponseSurvivesPeerWithInvalidName(t *testing.T) { - for _, tt := range []struct { - name string - badName string - }{ - {"empty given name", ""}, - {"over-long fqdn", strings.Repeat("a", types.MaxHostnameLength+1)}, - } { - t.Run(tt.name, func(t *testing.T) { - tmp := t.TempDir() - p4 := netip.MustParsePrefix("100.64.0.0/10") - p6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - cfg := &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{Path: tmp + "/h.db"}, - }, - PrefixV4: &p4, - PrefixV6: &p6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{Mode: types.PolicyModeDB}, - DERP: types.DERPConfig{ - DERPMap: &tailcfg.DERPMap{ - Regions: map[int]*tailcfg.DERPRegion{999: {RegionID: 999}}, - }, - }, - Tuning: types.Tuning{ - NodeStoreBatchSize: state.TestBatchSize, - NodeStoreBatchTimeout: state.TestBatchTimeout, - }, - } - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("u1") - n1 := database.CreateRegisteredNodeForTest(user, "n1") // viewer, valid - bad := database.CreateRegisteredNodeForTest(user, "bad") // peer, name corrupted below - good := database.CreateRegisteredNodeForTest(user, "good") // peer, valid control - - // Simulate a legacy/corrupt row that v29 loads verbatim. - require.NoError(t, database.DB. - Model(&types.Node{}). - Where("id = ?", bad.ID). - Update("given_name", tt.badName).Error) - require.NoError(t, database.Close()) - - s, err := state.NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Allow-all so n1 sees both peers; the bad one must still be dropped. - _, err = s.SetPolicy([]byte(`{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}`)) - require.NoError(t, err) - - m := &mapper{state: s, cfg: cfg} - capVer := tailcfg.CurrentCapabilityVersion - - resp, err := m.fullMapResponse(n1.ID, capVer) - require.NoError(t, err, "n1's map must build despite a peer with an invalid name") - require.NotNil(t, resp) - require.NotNil(t, resp.Node, "n1 must receive its own self node") - - peers := map[tailcfg.NodeID]bool{} - for _, p := range resp.Peers { - peers[p.ID] = true - } - - assert.False(t, peers[bad.ID.NodeID()], "the peer with an invalid name must be dropped") - assert.True(t, peers[good.ID.NodeID()], "valid peers must remain in the map") - }) - } -} - -// TestGenerateDNSConfigNilHostinfoNoPanic proves generateDNSConfig does not -// panic when a node's Hostinfo is nil (e.g. a legacy DB row with a NULL -// host_info column). addNextDNSMetadata dereferenced node.Hostinfo().OS() -// without the .Valid() guard its siblings (RequestTags, TailNode) apply, so -// building such a node's map crashed the server whenever a NextDNS resolver -// was configured. -func TestGenerateDNSConfigNilHostinfoNoPanic(t *testing.T) { - node := (&types.Node{ - Hostname: "legacy-node", - IPv4: iap("100.64.0.1"), - // Hostinfo intentionally nil, as a legacy NULL host_info row loads. - }).View() - - cfg := &types.Config{ - TailcfgDNSConfig: &tailcfg.DNSConfig{ - Resolvers: []*dnstype.Resolver{{Addr: "https://dns.nextdns.io/abc"}}, - }, - } - - require.NotPanics(t, func() { - generateDNSConfig(cfg, node, nil) - }, "generateDNSConfig must not panic when a node has nil Hostinfo") -} diff --git a/hscontrol/mapper/node_conn.go b/hscontrol/mapper/node_conn.go index 97d024a85..faa893faa 100644 --- a/hscontrol/mapper/node_conn.go +++ b/hscontrol/mapper/node_conn.go @@ -3,7 +3,6 @@ package mapper import ( "errors" "fmt" - "slices" "strconv" "sync" "sync/atomic" @@ -18,22 +17,12 @@ import ( "tailscale.com/tailcfg" ) -// errNoActiveConnections is returned by [multiChannelNodeConn.send] when a node -// has no active connections (disconnected but kept in the batcher for rapid -// reconnection). Callers must not update peer tracking state (lastSentPeers) -// after this error because the data was never delivered to any client. +// errNoActiveConnections is returned by send when a node has no active +// connections (disconnected but kept in the batcher for rapid reconnection). +// Callers must not update peer tracking state (lastSentPeers) after this +// error because the data was never delivered to any client. var errNoActiveConnections = errors.New("no active connections") -// errNoReadyConnections is returned by [multiChannelNodeConn.send] when the -// node's only connections are still waiting for their initial map -// ([Batcher.AddNode] has registered them but not yet delivered the first full -// response). Sending a delta now would make it the stream's first frame, which -// Tailscale clients reject ("initial MapResponse lacked Node") — tearing down -// the poll. Unlike [errNoActiveConnections], the change must be retried: the -// in-flight initial map may have been generated from a snapshot older than -// the change, so dropping it would lose the update. -var errNoReadyConnections = errors.New("no connections ready for updates") - // connectionEntry represents a single connection to a node. type connectionEntry struct { id string // unique connection ID @@ -43,13 +32,6 @@ type connectionEntry struct { stop func() lastUsed atomic.Int64 // Unix timestamp of last successful send closed atomic.Bool // Indicates if this connection has been closed - - // pendingInitial is set by [Batcher.AddNode] while this - // connection's initial map is still in flight, and cleared once it - // is delivered. Broadcast sends skip such connections so a delta - // can never become the stream's first frame ahead of the initial - // map. The zero value means the connection is ready. - pendingInitial atomic.Bool } // multiChannelNodeConn manages multiple concurrent connections for a single node. @@ -69,25 +51,18 @@ type multiChannelNodeConn struct { // workMu serializes change processing for this node across batch ticks. // Without this, two workers could process consecutive ticks' bundles - // concurrently, causing out-of-order [tailcfg.MapResponse] delivery and races - // on lastSentPeers (Clear+Store in [multiChannelNodeConn.updateSentPeers] vs - // Range in [multiChannelNodeConn.computePeerDiff]). + // concurrently, causing out-of-order MapResponse delivery and races + // on lastSentPeers (Clear+Store in updateSentPeers vs Range in + // computePeerDiff). workMu sync.Mutex - // inFlight is true while a batched work bundle for this node is queued or - // being processed. processBatchedChanges refuses to queue a second bundle - // while one is in flight (the new changes wait in pending), so a saturated - // worker pool cannot deliver tick N+1 before tick N: a non-FIFO workMu - // cannot reorder bundles that never coexist. - inFlight atomic.Bool - closeOnce sync.Once updateCount atomic.Int64 // disconnectedAt records when the last connection was removed. // nil means the node is considered connected (or newly created); // non-nil means the node disconnected at the stored timestamp. - // Used by [Batcher.cleanupOfflineNodes] to evict stale entries. + // Used by cleanupOfflineNodes to evict stale entries. disconnectedAt atomic.Pointer[time.Time] // lastSentPeers tracks which peers were last sent to this node. @@ -143,7 +118,9 @@ func (mc *multiChannelNodeConn) stopConnection(conn *connectionEntry) { // Caller must hold mc.mutex. func (mc *multiChannelNodeConn) removeConnectionAtIndexLocked(i int, stopConnection bool) *connectionEntry { conn := mc.connections[i] - mc.connections = slices.Delete(mc.connections, i, i+1) + copy(mc.connections[i:], mc.connections[i+1:]) + mc.connections[len(mc.connections)-1] = nil // release pointer for GC + mc.connections = mc.connections[:len(mc.connections)-1] if stopConnection { mc.stopConnection(conn) @@ -182,16 +159,6 @@ func (mc *multiChannelNodeConn) removeConnectionByChannel(c chan<- *tailcfg.MapR return false } -// detach removes the connection for the given channel and marks the node -// disconnected if no active connections remain. -func (mc *multiChannelNodeConn) detach(c chan<- *tailcfg.MapResponse) { - mc.removeConnectionByChannel(c) - - if !mc.hasActiveConnections() { - mc.markDisconnected() - } -} - // hasActiveConnections checks if the node has any active connections. func (mc *multiChannelNodeConn) hasActiveConnections() bool { mc.mutex.RLock() @@ -215,8 +182,8 @@ func (mc *multiChannelNodeConn) markConnected() { } // markDisconnected records the current time as the moment the node -// lost its last connection. Used by [Batcher.cleanupOfflineNodes] to -// determine how long the node has been offline. +// lost its last connection. Used by cleanupOfflineNodes to determine +// how long the node has been offline. func (mc *multiChannelNodeConn) markDisconnected() { now := time.Now() mc.disconnectedAt.Store(&now) @@ -251,17 +218,6 @@ func (mc *multiChannelNodeConn) appendPending(changes ...change.Change) { mc.pendingMu.Unlock() } -// prependPending puts changes at the head of the pending list, ahead of -// anything queued since. Used to retry changes that could not be -// delivered yet (initial map in flight): they were emitted before the -// currently pending ones, and order matters for stateful patches like -// online/offline. -func (mc *multiChannelNodeConn) prependPending(changes ...change.Change) { - mc.pendingMu.Lock() - mc.pending = append(changes, mc.pending...) - mc.pendingMu.Unlock() -} - // drainPending atomically removes and returns all pending changes. // Returns nil if there are no pending changes. func (mc *multiChannelNodeConn) drainPending() []change.Change { @@ -279,8 +235,8 @@ func (mc *multiChannelNodeConn) drainPending() []change.Change { // connection can block for up to 50ms), the method snapshots connections under // a read lock, sends without any lock held, then write-locks only to remove // failures. New connections added between the snapshot and cleanup are safe: -// they receive a full initial map via [Batcher.AddNode], so missing this update -// causes no data loss. +// they receive a full initial map via AddNode, so missing this update causes +// no data loss. func (mc *multiChannelNodeConn) send(data *tailcfg.MapResponse) error { if data == nil { return nil @@ -297,28 +253,11 @@ func (mc *multiChannelNodeConn) send(data *tailcfg.MapResponse) error { return errNoActiveConnections } - // Copy only connections whose initial map has been delivered. - // A connection still awaiting its initial map receives one - // (generated from the current snapshot) from [Batcher.AddNode]; - // pushing this update at it now would deliver a delta as the - // stream's first frame. - snapshot := make([]*connectionEntry, 0, len(mc.connections)) - - for _, conn := range mc.connections { - if !conn.pendingInitial.Load() { - snapshot = append(snapshot, conn) - } - } - + // Copy the slice so we can release the read lock before sending. + snapshot := make([]*connectionEntry, len(mc.connections)) + copy(snapshot, mc.connections) mc.mutex.RUnlock() - if len(snapshot) == 0 { - mc.log.Trace(). - Msg("send: connections present but none ready, requeue") - - return errNoReadyConnections - } - mc.log.Trace(). Int("total_connections", len(snapshot)). Msg("send: broadcasting") @@ -353,22 +292,32 @@ func (mc *multiChannelNodeConn) send(data *tailcfg.MapResponse) error { // Remove by pointer identity: only remove entries that still exist // in the current connections slice and match a failed pointer. // New connections added since the snapshot are not affected. - // DeleteFunc preserves order and zeroes trailing slots so removed - // *connectionEntry values are not retained by the backing array. - mc.connections = slices.DeleteFunc(mc.connections, func(conn *connectionEntry) bool { - if !slices.Contains(failed, conn) { - return false + failedSet := make(map[*connectionEntry]struct{}, len(failed)) + for _, f := range failed { + failedSet[f] = struct{}{} + } + + clean := mc.connections[:0] + for _, conn := range mc.connections { + if _, isFailed := failedSet[conn]; !isFailed { + clean = append(clean, conn) + } else { + mc.log.Debug(). + Str(zf.ConnID, conn.id). + Msg("send: removing failed connection") + // Tear down the owning session so the old serveLongPoll + // goroutine exits instead of lingering as a stale session. + mc.stopConnection(conn) } + } - mc.log.Debug(). - Str(zf.ConnID, conn.id). - Msg("send: removing failed connection") - // Tear down the owning session so the old serveLongPoll - // goroutine exits instead of lingering as a stale session. - mc.stopConnection(conn) + // Nil out trailing slots so removed *connectionEntry values + // are not retained by the backing array. + for i := len(clean); i < len(mc.connections); i++ { + mc.connections[i] = nil + } - return true - }) + mc.connections = clean mc.mutex.Unlock() } @@ -440,7 +389,7 @@ func (mc *multiChannelNodeConn) version() tailcfg.CapabilityVersion { return mc.connections[0].version } -// updateSentPeers updates the tracked peer state based on a sent [tailcfg.MapResponse]. +// updateSentPeers updates the tracked peer state based on a sent MapResponse. // This must be called after successfully sending a response to keep track of // what the client knows about, enabling accurate diffs for future updates. func (mc *multiChannelNodeConn) updateSentPeers(resp *tailcfg.MapResponse) { diff --git a/hscontrol/mapper/reconnect_cleanup_test.go b/hscontrol/mapper/reconnect_cleanup_test.go deleted file mode 100644 index 4d97a1e85..000000000 --- a/hscontrol/mapper/reconnect_cleanup_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package mapper - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// TestAddNodeReconnectNotOrphanedByCleanup ensures a node reconnecting via -// AddNode is not deleted from b.nodes by a concurrent cleanupOfflineNodes pass. -// AddNode must register the connection atomically with the get-or-create, so -// the offline-cleanup Compute either sees the active connection (and cancels) -// or runs first and AddNode recreates the entry — never leaving a live -// connection orphaned outside b.nodes. -func TestAddNodeReconnectNotOrphanedByCleanup(t *testing.T) { - testData, cleanup := setupBatcherWithTestData(t, NewBatcherAndMapper, 1, 1, normalBufferSize) - defer cleanup() - - b := testData.Batcher.Batcher - node := &testData.Nodes[0] - - testData.State.Connect(node.n.ID) - - go func() { - for range node.ch { - } - }() - - require.NoError(t, b.AddNode(node.n.ID, node.ch, tailcfg.CapabilityVersion(100), nil)) - - // Model a long-offline conn awaiting cleanup or a rapid reconnect. - nc, ok := b.nodes.Load(node.n.ID) - require.True(t, ok) - - nc.removeConnectionByChannel(node.ch) - - past := time.Now().Add(-(offlineNodeCleanupThreshold + time.Minute)) - nc.disconnectedAt.Store(&past) - require.False(t, nc.hasActiveConnections()) - - var wg sync.WaitGroup - - wg.Go(func() { - _ = b.AddNode(node.n.ID, node.ch, tailcfg.CapabilityVersion(100), nil) - }) - - wg.Go(func() { - b.cleanupOfflineNodes() - }) - - wg.Wait() - - assert.True(t, b.IsConnected(node.n.ID), - "reconnecting node was orphaned: live connection absent from b.nodes") -} diff --git a/hscontrol/mapper/tail_test.go b/hscontrol/mapper/tail_test.go index ee5da85c9..f973caaf8 100644 --- a/hscontrol/mapper/tail_test.go +++ b/hscontrol/mapper/tail_test.go @@ -70,6 +70,7 @@ func TestTailNode(t *testing.T) { Name: "empty", StableID: "0", HomeDERP: 0, + LegacyDERPString: "127.3.3.40:0", Hostinfo: hiview(tailcfg.Hostinfo{}), MachineAuthorized: true, @@ -147,7 +148,8 @@ func TestTailNode(t *testing.T) { PrimaryRoutes: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), }, - HomeDERP: 0, + HomeDERP: 0, + LegacyDERPString: "127.3.3.40:0", Hostinfo: hiview(tailcfg.Hostinfo{ RoutableIPs: []netip.Prefix{ tsaddr.AllIPv4(), @@ -184,6 +186,7 @@ func TestTailNode(t *testing.T) { Name: "minimal.example.com.", StableID: "0", HomeDERP: 0, + LegacyDERPString: "127.3.3.40:0", Hostinfo: hiview(tailcfg.Hostinfo{}), MachineAuthorized: true, diff --git a/hscontrol/metrics.go b/hscontrol/metrics.go index eb4fbf8c5..09cbc3930 100644 --- a/hscontrol/metrics.go +++ b/hscontrol/metrics.go @@ -1,6 +1,10 @@ package hscontrol import ( + "net/http" + "strconv" + + "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "tailscale.com/envknob" @@ -38,4 +42,63 @@ var ( Name: "mapresponse_ended_total", Help: "total count of new mapsessions ended", }, []string{"reason"}) + httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: prometheusNamespace, + Name: "http_duration_seconds", + Help: "Duration of HTTP requests.", + }, []string{"path"}) + httpCounter = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: prometheusNamespace, + Name: "http_requests_total", + Help: "Total number of http requests processed", + }, []string{"code", "method", "path"}, + ) ) + +// prometheusMiddleware implements mux.MiddlewareFunc. +func prometheusMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + route := mux.CurrentRoute(r) + path, _ := route.GetPathTemplate() + + // Ignore streaming and noise sessions + // it has its own router further down. + if path == "/ts2021" || path == "/machine/map" || path == "/derp" || path == "/derp/probe" || path == "/derp/latency-check" || path == "/bootstrap-dns" { + next.ServeHTTP(w, r) + return + } + + rw := &respWriterProm{ResponseWriter: w} + + timer := prometheus.NewTimer(httpDuration.WithLabelValues(path)) + + next.ServeHTTP(rw, r) + timer.ObserveDuration() + httpCounter.WithLabelValues(strconv.Itoa(rw.status), r.Method, path).Inc() + }) +} + +type respWriterProm struct { + http.ResponseWriter + + status int + written int64 + wroteHeader bool +} + +func (r *respWriterProm) WriteHeader(code int) { + r.status = code + r.wroteHeader = true + r.ResponseWriter.WriteHeader(code) +} + +func (r *respWriterProm) Write(b []byte) (int, error) { + if !r.wroteHeader { + r.WriteHeader(http.StatusOK) + } + + n, err := r.ResponseWriter.Write(b) + r.written += int64(n) + + return n, err +} diff --git a/hscontrol/noise.go b/hscontrol/noise.go index 3c6bb6e2a..d8a26cb1e 100644 --- a/hscontrol/noise.go +++ b/hscontrol/noise.go @@ -31,6 +31,9 @@ var ErrUnsupportedClientVersion = errors.New("unsupported client version") // ErrMissingURLParameter is returned when a required URL parameter is not provided. var ErrMissingURLParameter = errors.New("missing URL parameter") +// ErrUnsupportedURLParameterType is returned when a URL parameter has an unsupported type. +var ErrUnsupportedURLParameterType = errors.New("unsupported URL parameter type") + // ErrNoAuthSession is returned when an auth_id does not match any active auth session. var ErrNoAuthSession = errors.New("no auth session found") @@ -62,14 +65,14 @@ const ( // The first 9 bytes from the server to client over Noise are either an HTTP/2 // settings frame (a normal HTTP/2 setup) or, as Tailscale added later, an "early payload" - // header that's also 9 bytes long: 5 bytes ([earlyPayloadMagic]) followed by 4 bytes - // of length. Then that many bytes of JSON-encoded [tailcfg.EarlyNoise]. + // header that's also 9 bytes long: 5 bytes (earlyPayloadMagic) followed by 4 bytes + // of length. Then that many bytes of JSON-encoded tailcfg.EarlyNoise. // The early payload is optional. Some servers may not send it... But we do! earlyPayloadMagic = "\xff\xff\xffTS" // noiseBodyLimit is the maximum allowed request body size for Noise protocol - // handlers. This prevents unauthenticated OOM attacks via unbounded [io.ReadAll]. - // No legitimate Noise request ([tailcfg.MapRequest], [tailcfg.RegisterRequest], etc.) comes close + // handlers. This prevents unauthenticated OOM attacks via unbounded io.ReadAll. + // No legitimate Noise request (MapRequest, RegisterRequest, etc.) comes close // to this limit; typical payloads are a few KB. noiseBodyLimit int64 = 1048576 // 1 MiB ) @@ -81,13 +84,14 @@ type noiseServer struct { http2Server *http2.Server conn *controlbase.Conn machineKey key.MachinePublic + nodeKey key.NodePublic - // [tailcfg.EarlyNoise]-related stuff + // EarlyNoise-related stuff challenge key.ChallengePrivate protocolVersion int } -// NoiseUpgradeHandler is to upgrade the connection and hijack the [net.Conn] +// NoiseUpgradeHandler is to upgrade the connection and hijack the net.Conn // in order to use the Noise-based TS2021 protocol. Listens in /ts2021. func (h *Headscale) NoiseUpgradeHandler( writer http.ResponseWriter, @@ -132,7 +136,7 @@ func (h *Headscale) NoiseUpgradeHandler( // This router is served only over the Noise connection, and exposes only the new API. // // The HTTP2 server that exposes this router is created for - // a single hijacked connection from /ts2021, using [netutil.NewOneConnListener] + // a single hijacked connection from /ts2021, using netutil.NewOneConnListener r := chi.NewRouter() @@ -154,12 +158,7 @@ func (h *Headscale) NoiseUpgradeHandler( }, })) r.Use(middleware.RequestID) - - // The outer router resolved trusted_proxies on req before the - // upgrade; pin that value across the hijack so /machine/* logs the - // client IP instead of the reverse proxy's loopback peer. - r.Use(overrideRemoteAddr(req.RemoteAddr)) - + r.Use(middleware.RealIP) r.Use(middleware.RequestLogger(&zerologRequestLogger{})) r.Use(middleware.Recoverer) @@ -291,27 +290,13 @@ func rejectUnsupported( return false } -// overrideRemoteAddr returns middleware that pins r.RemoteAddr to addr. -// Used inside the Noise tunnel: the HTTP/2 server derives r.RemoteAddr -// from the hijacked TCP socket (the reverse proxy's loopback peer), so -// the outer request's resolved client IP must be carried across the -// hijack boundary by hand. -func overrideRemoteAddr(addr string) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = addr - next.ServeHTTP(w, r) - }) - } -} - func (ns *noiseServer) NotImplementedHandler(writer http.ResponseWriter, req *http.Request) { log.Trace().Caller().Str("path", req.URL.String()).Msg("not implemented handler hit") http.Error(writer, "Not implemented yet", http.StatusNotImplemented) } // PingResponseHandler handles HEAD requests from clients responding to a -// [tailcfg.PingRequest]. The client calls this endpoint to prove connectivity. +// PingRequest. The client calls this endpoint to prove connectivity. // The unguessable ping ID serves as authentication. func (h *Headscale) PingResponseHandler( writer http.ResponseWriter, @@ -335,27 +320,40 @@ func (h *Headscale) PingResponseHandler( } } -func stringParam(req *http.Request, key string) (string, error) { +func urlParam[T any](req *http.Request, key string) (T, error) { + var zero T + param := chi.URLParam(req, key) if param == "" { - return "", fmt.Errorf("%w: %s", ErrMissingURLParameter, key) + return zero, fmt.Errorf("%w: %s", ErrMissingURLParameter, key) } - return param, nil -} + var value T + switch any(value).(type) { + case string: + v, ok := any(param).(T) + if !ok { + return zero, fmt.Errorf("%w: %T", ErrUnsupportedURLParameterType, value) + } -func nodeIDParam(req *http.Request, key string) (types.NodeID, error) { - param := chi.URLParam(req, key) - if param == "" { - return 0, fmt.Errorf("%w: %s", ErrMissingURLParameter, key) + value = v + case types.NodeID: + id, err := types.ParseNodeID(param) + if err != nil { + return zero, fmt.Errorf("parsing %s: %w", key, err) + } + + v, ok := any(id).(T) + if !ok { + return zero, fmt.Errorf("%w: %T", ErrUnsupportedURLParameterType, value) + } + + value = v + default: + return zero, fmt.Errorf("%w: %T", ErrUnsupportedURLParameterType, value) } - id, err := types.ParseNodeID(param) - if err != nil { - return 0, fmt.Errorf("parsing %s: %w", key, err) - } - - return id, nil + return value, nil } // SSHActionHandler handles the /ssh-action endpoint, returning a @@ -365,7 +363,7 @@ func (ns *noiseServer) SSHActionHandler( writer http.ResponseWriter, req *http.Request, ) { - srcNodeID, err := nodeIDParam(req, "src_node_id") + srcNodeID, err := urlParam[types.NodeID](req, "src_node_id") if err != nil { httpError(writer, NewHTTPError( http.StatusBadRequest, @@ -376,7 +374,7 @@ func (ns *noiseServer) SSHActionHandler( return } - dstNodeID, err := nodeIDParam(req, "dst_node_id") + dstNodeID, err := urlParam[types.NodeID](req, "dst_node_id") if err != nil { httpError(writer, NewHTTPError( http.StatusBadRequest, @@ -455,12 +453,12 @@ func (ns *noiseServer) SSHActionHandler( } // sshAction resolves the SSH action for the given request parameters. -// It returns the action to send to the client, or an [HTTPError] on failure. +// It returns the action to send to the client, or an HTTPError on failure. // // Three cases: // 1. Initial request, auto-approved — source recently authenticated // within the check period, accept immediately. -// 2. Initial request, needs auth — build a [tailcfg.SSHAction.HoldAndDelegate] URL and +// 2. Initial request, needs auth — build a HoldAndDelegate URL and // wait for the user to authenticate. // 3. Follow-up request — an auth_id is present, wait for the auth // verdict and accept or reject. @@ -512,7 +510,7 @@ func (ns *noiseServer) sshAction( } // sshActionHoldAndDelegate creates a new auth session bound to the -// (src, dst) pair and returns a [tailcfg.SSHAction.HoldAndDelegate] action that directs the +// (src, dst) pair and returns a HoldAndDelegate action that directs the // client to authenticate. func (ns *noiseServer) sshActionHoldAndDelegate( reqLog zerolog.Logger, @@ -594,19 +592,6 @@ func (ns *noiseServer) sshActionFollowUp( auth, ok := ns.headscale.state.GetAuthCacheEntry(authID) if !ok { - // The session is gone (expired, evicted, or lost on a control-plane - // restart). A bare error dead-ends the client: it keeps polling this - // now-defunct auth_id until the SSH connection times out. Re-delegate - // so a still-required check can complete instead. - if checkFound { - reqLog.Info().Caller(). - Msg("SSH check auth session missing; re-delegating") - - return ns.sshActionHoldAndDelegate( - reqLog, action, srcNodeID, dstNodeID, - ) - } - return nil, NewHTTPError( http.StatusBadRequest, "Invalid auth_id", @@ -647,8 +632,8 @@ func (ns *noiseServer) sshActionFollowUp( case <-ctx.Done(): // The client disconnected (or its request timed out) before the // auth session resolved. Return an error so the parked goroutine - // is freed; without this select [noiseServer.sshActionFollowUp] would block - // until the cache eviction callback signalled [types.AuthRequest.FinishAuth], which + // is freed; without this select sshActionFollowUp would block + // until the cache eviction callback signalled FinishAuth, which // could be up to register_cache_expiration (15 minutes). return nil, NewHTTPError( http.StatusUnauthorized, @@ -685,8 +670,8 @@ func (ns *noiseServer) sshActionFollowUp( // This is the busiest endpoint, as it keeps the HTTP long poll that updates // the clients when something in the network changes. // -// The clients POST stuff like [tailcfg.Hostinfo] and their Endpoints here, but -// only after their first request (marked with the [tailcfg.MapRequest.ReadOnly] field). +// The clients POST stuff like HostInfo and their Endpoints here, but +// only after their first request (marked with the ReadOnly field). // // At this moment the updates are sent in a quite horrendous way, but they kinda work. func (ns *noiseServer) PollNetMapHandler( @@ -712,6 +697,8 @@ func (ns *noiseServer) PollNetMapHandler( return } + ns.nodeKey = nv.NodeKey() + sess := ns.headscale.newMapSession(req.Context(), mapRequest, writer, nv.AsStruct()) sess.log.Trace().Caller().Msg("a node sending a MapRequest with Noise protocol") @@ -747,6 +734,8 @@ func (ns *noiseServer) RegistrationHandler( return ®Req, regErr(err) } + ns.nodeKey = regReq.NodeKey + resp, err = ns.headscale.handleRegister(req.Context(), regReq, ns.conn.Peer()) if err != nil { if httpErr, ok := errors.AsType[HTTPError](err); ok { diff --git a/hscontrol/noise_test.go b/hscontrol/noise_test.go index 99b3ec4bf..cf97a216e 100644 --- a/hscontrol/noise_test.go +++ b/hscontrol/noise_test.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/http/httptest" - "net/url" "strconv" "testing" @@ -22,8 +21,8 @@ import ( // newNoiseRouterWithBodyLimit builds a chi router with the same body-limit // middleware used in the real Noise router but wired to a test handler that -// captures the [io.ReadAll] result. This lets us verify the limit without -// needing a full [Headscale] instance. +// captures the io.ReadAll result. This lets us verify the limit without +// needing a full Headscale instance. func newNoiseRouterWithBodyLimit(readBody *[]byte, readErr *error) http.Handler { r := chi.NewRouter() r.Use(func(next http.Handler) http.Handler { @@ -160,7 +159,7 @@ func TestNoiseBodyLimit_AtExactLimit(t *testing.T) { } // TestPollNetMapHandler_OversizedBody calls the real handler with a -// [http.MaxBytesReader]-wrapped body to verify it fails gracefully (json decode +// MaxBytesReader-wrapped body to verify it fails gracefully (json decode // error on truncated data) rather than consuming unbounded memory. func TestPollNetMapHandler_OversizedBody(t *testing.T) { t.Parallel() @@ -174,12 +173,12 @@ func TestPollNetMapHandler_OversizedBody(t *testing.T) { ns.PollNetMapHandler(rec, req) - // Body is truncated → [json.Decoder.Decode] fails → [httpError] returns 500. + // Body is truncated → json.Decode fails → httpError returns 500. assert.Equal(t, http.StatusInternalServerError, rec.Code) } // TestRegistrationHandler_OversizedBody calls the real handler with a -// [http.MaxBytesReader]-wrapped body to verify it returns an error response +// MaxBytesReader-wrapped body to verify it returns an error response // rather than consuming unbounded memory. func TestRegistrationHandler_OversizedBody(t *testing.T) { t.Parallel() @@ -193,8 +192,8 @@ func TestRegistrationHandler_OversizedBody(t *testing.T) { ns.RegistrationHandler(rec, req) - // [json.Decoder.Decode] returns [http.MaxBytesError] → [regErr] wraps it → handler writes - // a [tailcfg.RegisterResponse] with the error and then [rejectUnsupported] kicks in + // json.Decode returns MaxBytesError → regErr wraps it → handler writes + // a RegisterResponse with the error and then rejectUnsupported kicks in // for version 0 → returns 400. assert.Equal(t, http.StatusBadRequest, rec.Code) } @@ -237,7 +236,7 @@ func TestSSHActionRoute_OldPathReturns404(t *testing.T) { } // newSSHActionRequest builds an httptest request with the chi URL params -// [noiseServer.SSHActionHandler] reads (src_node_id and dst_node_id), so the handler +// SSHActionHandler reads (src_node_id and dst_node_id), so the handler // can be exercised directly without going through the chi router. func newSSHActionRequest(t *testing.T, src, dst types.NodeID) *http.Request { t.Helper() @@ -254,8 +253,8 @@ func newSSHActionRequest(t *testing.T, src, dst types.NodeID) *http.Request { } // putTestNodeInStore creates a node via the database test helper and -// also stages it into the in-memory [state.NodeStore] so handlers that read -// [state.NodeStore]-backed APIs (e.g. [state.State.GetNodeByID]) can see it. +// also stages it into the in-memory NodeStore so handlers that read +// NodeStore-backed APIs (e.g. State.GetNodeByID) can see it. func putTestNodeInStore(t *testing.T, app *Headscale, user *types.User, hostname string) *types.Node { t.Helper() @@ -277,7 +276,7 @@ func TestSSHActionHandler_RejectsRogueMachineKey(t *testing.T) { src := putTestNodeInStore(t, app, user, "src-node") dst := putTestNodeInStore(t, app, user, "dst-node") - // [noiseServer] carries the wrong machine key — a fresh throwaway key, + // noiseServer carries the wrong machine key — a fresh throwaway key, // not dst.MachineKey. rogue := key.NewMachine().Public() require.NotEqual(t, dst.MachineKey, rogue, "test sanity: rogue key must differ from dst") @@ -369,171 +368,3 @@ func TestSSHActionFollowUp_RejectsBindingMismatch(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, rec.Code, "binding mismatch must be rejected with 401") } - -// TestOverrideRemoteAddr asserts the middleware used inside the Noise -// tunnel pins r.RemoteAddr to the value captured from the outer -// (pre-hijack) request, so /machine/* requests log the trusted-proxy -// resolved client IP instead of the hijacked TCP socket's loopback peer. -func TestOverrideRemoteAddr(t *testing.T) { - t.Parallel() - - const clientAddr = "192.168.91.240" - - r := chi.NewRouter() - r.Use(overrideRemoteAddr(clientAddr)) - - var observed string - - r.Get("/x", func(w http.ResponseWriter, r *http.Request) { - observed = r.RemoteAddr - - w.WriteHeader(http.StatusOK) - }) - - req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/x", nil) - req.RemoteAddr = "127.0.0.1:44388" - - r.ServeHTTP(httptest.NewRecorder(), req) - - assert.Equal(t, clientAddr, observed) -} - -// TestSSHActionHoldAndDelegate_PersistsAuthSession guards the happy path: the -// initial SSH-check poll returns a HoldAndDelegate URL carrying an auth_id, and -// that auth session must remain in the cache for the follow-up poll to find. -func TestSSHActionHoldAndDelegate_PersistsAuthSession(t *testing.T) { - t.Parallel() - - app := createTestApp(t) - user := app.state.CreateUserForTest("ssh-persist-user") - src := putTestNodeInStore(t, app, user, "src-node") - dst := putTestNodeInStore(t, app, user, "dst-node") - - ns := &noiseServer{headscale: app, machineKey: dst.MachineKey} - - rec := httptest.NewRecorder() - ns.SSHActionHandler(rec, newSSHActionRequest(t, src.ID, dst.ID)) - require.Equal(t, http.StatusOK, rec.Code, "initial poll body=%s", rec.Body.String()) - - var action tailcfg.SSHAction - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &action)) - require.NotEmpty(t, action.HoldAndDelegate, "expected HoldAndDelegate, got %+v", action) - - u, err := url.Parse(action.HoldAndDelegate) - require.NoError(t, err) - - authIDStr := u.Query().Get("auth_id") - require.NotEmpty(t, authIDStr, "HoldAndDelegate URL missing auth_id: %s", action.HoldAndDelegate) - - authID, err := types.AuthIDFromString(authIDStr) - require.NoError(t, err) - - _, ok := app.state.GetAuthCacheEntry(authID) - require.True(t, ok, "auth session %s must persist after HoldAndDelegate", authID) -} - -// TestSSHActionHandler_RejectsMissingSessionWithoutCheck verifies that without -// an SSH check covering the pair, a follow-up poll for an unknown auth_id is a -// genuinely bogus request and is rejected. The re-delegation behaviour for a -// missing session (issue #3305, exercised end to end with a real client in the -// servertest package) applies only when the pair is still subject to a check. -func TestSSHActionHandler_RejectsMissingSessionWithoutCheck(t *testing.T) { - t.Parallel() - - app := createTestApp(t) - user := app.state.CreateUserForTest("ssh-nocheck-user") - src := putTestNodeInStore(t, app, user, "src-node") - dst := putTestNodeInStore(t, app, user, "dst-node") - - // No SSH-check policy is set, so the pair is not subject to a check. - _, checkFound := app.state.SSHCheckParams(src.ID, dst.ID) - require.False(t, checkFound, "test setup: pair must not be subject to a check") - - ns := &noiseServer{headscale: app, machineKey: dst.MachineKey} - - missing := types.MustAuthID() - - rec := httptest.NewRecorder() - ns.SSHActionHandler(rec, newSSHActionFollowUpRequest(t, src.ID, dst.ID, missing)) - require.Equal(t, http.StatusBadRequest, rec.Code, - "a bogus auth_id with no active check must be rejected, body=%s", rec.Body.String()) -} - -// TestTS2021Route_AcceptsGETAndPOST reproduces a regression where the -// browser/WASM control client could not connect. Tailscale's JS/WASM control -// client opens /ts2021 as a WebSocket, which is an HTTP GET upgrade; the native -// Go client uses an HTTP POST upgrade. The gorilla->chi router migration -// registered /ts2021 for POST only, so the GET WebSocket handshake was rejected -// with 405 Method Not Allowed by the router before it could reach -// NoiseUpgradeHandler. Both methods must route to the handler. -// -// NoiseUpgradeHandler dispatches on the Upgrade header, not the HTTP method, so -// once the route is reachable the handler handles both upgrade styles. The -// httptest recorder is not an http.Hijacker, so the upgrade itself fails past -// the router (501 for the WebSocket path, 400 for the native path) — the point -// is only that neither is 405, i.e. the router no longer rejects GET early. -func TestTS2021Route_AcceptsGETAndPOST(t *testing.T) { - t.Parallel() - - handler := createTestApp(t).HTTPHandler() - - tests := []struct { - name string - method string - headers map[string]string - }{ - { - name: "websocket_get_from_wasm_client", - method: http.MethodGet, - headers: map[string]string{ - "Connection": "Upgrade", - "Upgrade": "websocket", - "Sec-WebSocket-Version": "13", - "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", - "Sec-WebSocket-Protocol": "tailscale-control-protocol", - }, - }, - { - name: "native_post_upgrade", - method: http.MethodPost, - headers: map[string]string{ - "Connection": "upgrade", - "Upgrade": "tailscale-control-protocol", - "X-Tailscale-Handshake": "AAAA", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - req := httptest.NewRequestWithContext(context.Background(), tt.method, - "/ts2021?X-Tailscale-Handshake=AAAA", nil) - for k, v := range tt.headers { - req.Header.Set(k, v) - } - - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - assert.NotEqual(t, http.StatusMethodNotAllowed, rec.Code, - "%s /ts2021 must reach NoiseUpgradeHandler, not be rejected by the router with 405", - tt.method) - }) - } -} - -// newSSHActionFollowUpRequest is like newSSHActionRequest but carries the -// auth_id query parameter that marks a follow-up poll. -func newSSHActionFollowUpRequest(t *testing.T, src, dst types.NodeID, authID types.AuthID) *http.Request { - t.Helper() - - req := newSSHActionRequest(t, src, dst) - - q := req.URL.Query() - q.Set("auth_id", authID.String()) - req.URL.RawQuery = q.Encode() - - return req -} diff --git a/hscontrol/oidc.go b/hscontrol/oidc.go index bd660e4d1..5b5953335 100644 --- a/hscontrol/oidc.go +++ b/hscontrol/oidc.go @@ -20,7 +20,6 @@ import ( "github.com/juanfont/headscale/hscontrol/util" "github.com/rs/zerolog/log" "golang.org/x/oauth2" - "tailscale.com/util/rands" ) const ( @@ -28,15 +27,15 @@ const ( defaultOAuthOptionsCount = 3 authCacheExpiration = time.Minute * 15 - // authCacheMaxEntries bounds the OIDC state→[AuthInfo] cache to prevent + // authCacheMaxEntries bounds the OIDC state→AuthInfo cache to prevent // unauthenticated cache-fill DoS via repeated /register/{auth_id} or // /auth/{auth_id} GETs that mint OIDC state cookies. authCacheMaxEntries = 1024 // cookieNamePrefixLen is the number of leading characters from a - // state/nonce value that [getCookieName] splices into the cookie name. + // state/nonce value that getCookieName splices into the cookie name. // State and nonce values that are shorter than this are rejected at - // the callback boundary so [getCookieName] cannot panic on a slice + // the callback boundary so getCookieName cannot panic on a slice // out-of-range. cookieNamePrefixLen = 6 ) @@ -55,7 +54,6 @@ var ( "authenticated principal does not match any allowed user", ) errOIDCUnverifiedEmail = errors.New("authenticated principal has an unverified email") - errInvalidPKCEMethod = errors.New("invalid pkce.method") ) // AuthInfo contains both auth ID and verifier information for OIDC validation. @@ -71,7 +69,7 @@ type AuthProviderOIDC struct { cfg *types.OIDCConfig // authCache holds auth information between the auth and the callback - // steps. It is a bounded [expirable.LRU] keyed by OIDC state, evicting oldest + // steps. It is a bounded LRU keyed by OIDC state, evicting oldest // entries to keep the cache footprint constant under attack. authCache *expirable.LRU[string, AuthInfo] @@ -85,9 +83,9 @@ func NewAuthProviderOIDC( serverURL string, cfg *types.OIDCConfig, ) (*AuthProviderOIDC, error) { - // Use the caller's context (bounded, see app.go) so a slow or unreachable - // issuer fails discovery within the timeout instead of hanging startup. - oidcProvider, err := oidc.NewProvider(ctx, cfg.Issuer) + var err error + // grab oidc config if it hasn't been already + oidcProvider, err := oidc.NewProvider(context.Background(), cfg.Issuer) //nolint:contextcheck if err != nil { return nil, fmt.Errorf("creating OIDC provider from issuer config: %w", err) } @@ -117,17 +115,11 @@ func NewAuthProviderOIDC( }, nil } -// cookiesSecure reports whether the OIDC cookies should carry the Secure flag. -// It keys off the configured server_url scheme, not req.TLS, so cookies stay -// Secure behind a TLS-terminating reverse proxy (where the proxy→Headscale hop -// is plain HTTP and req.TLS is nil). Deriving it from config avoids trusting a -// spoofable X-Forwarded-Proto header. -func (a *AuthProviderOIDC) cookiesSecure() bool { - return strings.HasPrefix(a.serverURL, "https://") -} - func (a *AuthProviderOIDC) AuthURL(authID types.AuthID) string { - return authPathURL(a.serverURL, "auth", authID) + return fmt.Sprintf( + "%s/auth/%s", + strings.TrimSuffix(a.serverURL, "/"), + authID.String()) } func (a *AuthProviderOIDC) AuthHandler( @@ -138,7 +130,10 @@ func (a *AuthProviderOIDC) AuthHandler( } func (a *AuthProviderOIDC) RegisterURL(authID types.AuthID) string { - return authPathURL(a.serverURL, "register", authID) + return fmt.Sprintf( + "%s/register/%s", + strings.TrimSuffix(a.serverURL, "/"), + authID.String()) } // RegisterHandler registers the OIDC callback handler with the given router. @@ -165,10 +160,18 @@ func (a *AuthProviderOIDC) authHandler( } // Set the state and nonce cookies to protect against CSRF attacks - state := setCSRFCookie(writer, req, "state", a.cookiesSecure()) + state, err := setCSRFCookie(writer, req, "state") + if err != nil { + httpUserError(writer, err) + return + } // Set the state and nonce cookies to protect against CSRF attacks - nonce := setCSRFCookie(writer, req, "nonce", a.cookiesSecure()) + nonce, err := setCSRFCookie(writer, req, "nonce") + if err != nil { + httpUserError(writer, err) + return + } registrationInfo := AuthInfo{ AuthID: authID, @@ -189,12 +192,6 @@ func (a *AuthProviderOIDC) authHandler( case types.PKCEMethodPlain: // oauth2 does not have a plain challenge option, so we add it manually extras = append(extras, oauth2.SetAuthURLParam("code_challenge_method", "plain"), oauth2.SetAuthURLParam("code_challenge", verifier)) - default: - // An unknown method must not silently emit no challenge: a - // verifier was generated and is sent at token exchange, so a - // missing challenge degrades to no-PKCE without anyone noticing. - httpError(writer, NewHTTPError(http.StatusInternalServerError, "internal server error", fmt.Errorf("%w: %q", errInvalidPKCEMethod, a.cfg.PKCE.Method))) - return } } @@ -272,11 +269,6 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler( return } - // The state/nonce cookies have served their CSRF purpose; clear them so a - // single-use pair does not linger in the browser until MaxAge. - clearOIDCCallbackCookie(writer, stateCookieName) - clearOIDCCallbackCookie(writer, nonceCookieName) - nodeExpiry := a.determineNodeExpiry(idToken.Expiry) var claims types.OIDCClaims @@ -294,9 +286,9 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler( util.LogErr(err, "could not get userinfo; only using claims from id token") } - // The [oidc.UserInfo] type only decodes some fields (Subject, Profile, Email, EmailVerified). + // The oidc.UserInfo type only decodes some fields (Subject, Profile, Email, EmailVerified). // We are interested in other fields too (e.g. groups are required for allowedGroups) so we - // decode into our own [types.OIDCUserInfo] type using the underlying claims struct. + // decode into our own OIDCUserInfo type using the underlying claims struct. var userinfo2 types.OIDCUserInfo if userinfo != nil && userinfo.Claims(&userinfo2) == nil && userinfo2.Sub == claims.Sub { // Update the user with the userinfo claims (with id token claims as fallback). @@ -310,6 +302,8 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler( if userinfo2.Groups != nil { claims.Groups = userinfo2.Groups } + } else { + util.LogErr(err, "could not get userinfo; only using claims from id token") } // The user claims are now updated from the userinfo endpoint so we can verify the user @@ -450,10 +444,10 @@ func extractCodeAndStateParamFromRequest( return "", "", NewHTTPError(http.StatusBadRequest, "missing code or state parameter", errEmptyOIDCCallbackParams) } - // Reject states that are too short for [getCookieName] to splice + // Reject states that are too short for getCookieName to splice // into a cookie name. Without this guard a request with // ?state=abc panics on the slice out-of-range and is recovered by - // chi's [middleware.Recoverer], amplifying small-DoS log noise. + // chi's middleware.Recoverer, amplifying small-DoS log noise. if len(state) < cookieNamePrefixLen { return "", "", NewHTTPError(http.StatusBadRequest, "invalid state parameter", errOIDCStateTooShort) } @@ -485,7 +479,7 @@ func (a *AuthProviderOIDC) getOauth2Token( return nil, NewHTTPError(http.StatusForbidden, "invalid code", fmt.Errorf("exchanging code for token: %w", err)) } - return oauth2Token, nil + return oauth2Token, err } // extractIDToken extracts the ID token from the oauth2 token. @@ -558,15 +552,15 @@ func validateOIDCAllowedUsers( // // The following tests are always applied: // -// - [validateOIDCAllowedGroups] +// - validateOIDCAllowedGroups // // The following tests are applied if cfg.EmailVerifiedRequired=false // or claims.email_verified=true: // -// - [validateOIDCAllowedDomains] -// - [validateOIDCAllowedUsers] +// - validateOIDCAllowedDomains +// - validateOIDCAllowedUsers // -// NOTE that, contrary to the function name, [validateOIDCAllowedUsers] +// NOTE that, contrary to the function name, validateOIDCAllowedUsers // only checks the email address -- not the username. func doOIDCAuthorization( cfg *types.OIDCConfig, @@ -603,17 +597,13 @@ func doOIDCAuthorization( return nil } -// getAuthInfoFromState retrieves and consumes the auth info for a state. The -// entry is removed on read so a state is single-use: a replayed callback cannot -// resolve the same auth session twice, even within the cache TTL. +// getAuthInfoFromState retrieves the registration ID from the state. func (a *AuthProviderOIDC) getAuthInfoFromState(state string) *AuthInfo { authInfo, ok := a.authCache.Get(state) if !ok { return nil } - a.authCache.Remove(state) - return &authInfo } @@ -667,31 +657,8 @@ func (a *AuthProviderOIDC) createOrUpdateUserFromClaim( // browser do not collide. const registerConfirmCSRFCookie = "headscale_register_confirm" -// setRegisterConfirmCookie writes the per-session register-confirm CSRF -// cookie. Pass the CSRF token and authCacheExpiration seconds to set it; -// pass ("", -1) to clear it after the registration is finalised. -func setRegisterConfirmCookie( - writer http.ResponseWriter, - req *http.Request, - authID types.AuthID, - value string, - maxAge int, - secure bool, -) { - //nolint:gosec // G124: Secure from server_url scheme or req.TLS; HttpOnly + SameSite already set - http.SetCookie(writer, &http.Cookie{ - Name: registerConfirmCSRFCookie, - Value: value, - Path: "/register/confirm/" + authID.String(), - MaxAge: maxAge, - Secure: secure || req.TLS != nil, - HttpOnly: true, - SameSite: http.SameSiteStrictMode, - }) -} - // renderRegistrationConfirmInterstitial captures the resolved OIDC -// identity and node expiry into the cached [types.AuthRequest], sets the CSRF +// identity and node expiry into the cached AuthRequest, sets the CSRF // cookie, and renders the confirmation page that the user must // explicitly submit before the registration is finalised. func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial( @@ -718,7 +685,12 @@ func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial( return } - csrf := rands.HexString(32) + csrf, err := util.GenerateRandomStringURLSafe(32) + if err != nil { + httpUserError(writer, fmt.Errorf("generating csrf token: %w", err)) + + return + } authReq.SetPendingConfirmation(&types.PendingRegistrationConfirmation{ UserID: user.ID, @@ -726,7 +698,15 @@ func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial( CSRF: csrf, }) - setRegisterConfirmCookie(writer, req, authID, csrf, int(authCacheExpiration.Seconds()), a.cookiesSecure()) + http.SetCookie(writer, &http.Cookie{ + Name: registerConfirmCSRFCookie, + Value: csrf, + Path: "/register/confirm/" + authID.String(), + MaxAge: int(authCacheExpiration.Seconds()), + Secure: req.TLS != nil, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + }) regData := authReq.RegistrationData() @@ -753,7 +733,7 @@ func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial( // RegisterConfirmHandler is the POST endpoint behind the OIDC // registration confirmation interstitial. It validates the CSRF cookie // against the form-submitted token, finalises the registration via -// [AuthProviderOIDC.handleRegistration], and renders the success page. +// handleRegistration, and renders the success page. func (a *AuthProviderOIDC) RegisterConfirmHandler( writer http.ResponseWriter, req *http.Request, @@ -843,14 +823,22 @@ func (a *AuthProviderOIDC) RegisterConfirmHandler( } // Clear the CSRF cookie now that the registration is final. - setRegisterConfirmCookie(writer, req, authID, "", -1, a.cookiesSecure()) + http.SetCookie(writer, &http.Cookie{ + Name: registerConfirmCSRFCookie, + Value: "", + Path: "/register/confirm/" + authID.String(), + MaxAge: -1, + Secure: req.TLS != nil, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + }) content := renderRegistrationSuccessTemplate(user, newNode) writer.Header().Set("Content-Type", "text/html; charset=utf-8") writer.WriteHeader(http.StatusOK) - // [renderRegistrationSuccessTemplate]'s output only embeds + // renderRegistrationSuccessTemplate's output only embeds // HTML-escaped values from a server-side template, so the gosec // XSS warning is a false positive here. if _, err := writer.Write(content.Bytes()); err != nil { //nolint:noinlineerr,gosec @@ -929,46 +917,29 @@ func renderAuthSuccessTemplate( return bytes.NewBufferString(templates.AuthSuccess(result).Render()) } -// getCookieName generates a unique cookie name based on a cookie value. It -// uses at most [cookieNamePrefixLen] bytes of value, and fewer if value is -// shorter, so a short value (e.g. a malformed nonce from a misbehaving IdP) -// yields a non-matching name rather than panicking with slice-out-of-range. +// getCookieName generates a unique cookie name based on a cookie value. +// Callers must ensure value has at least cookieNamePrefixLen bytes; +// extractCodeAndStateParamFromRequest enforces this for the state +// parameter, and setCSRFCookie always supplies a 64-byte random value. func getCookieName(baseName, value string) string { - n := min(len(value), cookieNamePrefixLen) - - return fmt.Sprintf("%s_%s", baseName, value[:n]) + return fmt.Sprintf("%s_%s", baseName, value[:cookieNamePrefixLen]) } -// clearOIDCCallbackCookie expires a /oidc/callback cookie by name. Matching the -// path the cookie was set with is required for the browser to drop it. -func clearOIDCCallbackCookie(w http.ResponseWriter, name string) { - //nolint:gosec // G124: a deletion cookie (empty value, MaxAge<0); security attributes are moot - http.SetCookie(w, &http.Cookie{ - Name: name, - Path: "/oidc/callback", - MaxAge: -1, - }) -} +func setCSRFCookie(w http.ResponseWriter, r *http.Request, name string) (string, error) { + val, err := util.GenerateRandomStringURLSafe(64) + if err != nil { + return val, err + } -func setCSRFCookie(w http.ResponseWriter, r *http.Request, name string, secure bool) string { - val := rands.HexString(64) - - //nolint:gosec // G124: Secure from server_url scheme or req.TLS; HttpOnly + SameSite set below c := &http.Cookie{ Path: "/oidc/callback", Name: getCookieName(name, val), Value: val, MaxAge: int(time.Hour.Seconds()), - Secure: secure || r.TLS != nil, + Secure: r.TLS != nil, HttpOnly: true, - // Lax, not Strict: the OIDC callback is a cross-site top-level GET - // redirect from the IdP that must still carry this cookie. Strict - // would drop it and break login. Setting it explicitly also stops - // pre-Lax-default browsers from sending it on other cross-site - // requests. - SameSite: http.SameSiteLaxMode, } http.SetCookie(w, c) - return val + return val, nil } diff --git a/hscontrol/oidc_confirm_test.go b/hscontrol/oidc_confirm_test.go index bb4e4ee5a..74af90ec7 100644 --- a/hscontrol/oidc_confirm_test.go +++ b/hscontrol/oidc_confirm_test.go @@ -24,7 +24,6 @@ func newConfirmRequest(t *testing.T, authID types.AuthID, formCSRF, cookieCSRF s form, ) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - //nolint:gosec // G124: test fixture req.AddCookie(&http.Cookie{ Name: registerConfirmCSRFCookie, Value: cookieCSRF, @@ -68,7 +67,7 @@ func TestRegisterConfirmHandler_RejectsCSRFMismatch(t *testing.T) { "CSRF cookie/form mismatch must be rejected with 403") // And the registration must still be pending — the rejected POST - // must not have called [AuthProviderOIDC.handleRegistration]. + // must not have called handleRegistration. cached, ok := app.state.GetAuthCacheEntry(authID) require.True(t, ok, "rejected POST must not evict the cached registration") require.NotNil(t, cached.PendingConfirmation(), diff --git a/hscontrol/oidc_cookiename_test.go b/hscontrol/oidc_cookiename_test.go deleted file mode 100644 index 6adca0bf9..000000000 --- a/hscontrol/oidc_cookiename_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package hscontrol - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestGetCookieNameShortValue ensures a value shorter than the prefix length -// (e.g. a malformed nonce from a misbehaving IdP) does not panic with -// slice-out-of-range; it uses the available bytes instead. -func TestGetCookieNameShortValue(t *testing.T) { - require.NotPanics(t, func() { - assert.Equal(t, "nonce_ab", getCookieName("nonce", "ab")) - }) - - assert.Equal(t, "nonce_abcdef", getCookieName("nonce", "abcdef")) - assert.Equal(t, "nonce_abcdef", getCookieName("nonce", "abcdefghij")) -} diff --git a/hscontrol/oidc_test.go b/hscontrol/oidc_test.go index 04d331649..c6ce382c5 100644 --- a/hscontrol/oidc_test.go +++ b/hscontrol/oidc_test.go @@ -1,15 +1,9 @@ package hscontrol import ( - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/hashicorp/golang-lru/v2/expirable" "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDoOIDCAuthorization(t *testing.T) { @@ -177,87 +171,3 @@ func TestDoOIDCAuthorization(t *testing.T) { }) } } - -// TestSetCSRFCookieSameSite verifies the OIDC state/nonce CSRF cookies carry an -// explicit SameSite=Lax attribute. Lax (not Strict) is required because the -// OIDC callback is a cross-site top-level GET navigation from the IdP that must -// still carry the cookie — Strict would drop it and break login. The cookie -// previously set no SameSite (despite a comment claiming it did), leaving -// browsers that do not default to Lax sending it on cross-site requests. -func TestSetCSRFCookieSameSite(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/auth/abcdef0123456789", nil) - - setCSRFCookie(w, r, "state", false) - - cookies := w.Result().Cookies() - require.Len(t, cookies, 1) - assert.Equal(t, http.SameSiteLaxMode, cookies[0].SameSite, - "OIDC CSRF cookie must explicitly set SameSite=Lax") -} - -// TestExtractCodeAndStateParam covers the callback's first trust-boundary -// checks: both params required, and a too-short state is rejected before -// getCookieName can slice out of range. -func TestExtractCodeAndStateParam(t *testing.T) { - _, _, err := extractCodeAndStateParamFromRequest( - httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/oidc/callback", nil)) - require.Error(t, err) - - _, _, err = extractCodeAndStateParamFromRequest( - httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/oidc/callback?code=c&state=abc", nil)) - require.ErrorIs(t, err, errOIDCStateTooShort) - - code, state, err := extractCodeAndStateParamFromRequest( - httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/oidc/callback?code=c&state=abcdef0123", nil)) - require.NoError(t, err) - assert.Equal(t, "c", code) - assert.Equal(t, "abcdef0123", state) -} - -// TestGetAuthInfoFromStateSingleUse asserts a consumed OIDC state cannot be -// resolved twice, so a replayed callback cannot re-bind the same session. -func TestGetAuthInfoFromStateSingleUse(t *testing.T) { - a := &AuthProviderOIDC{ - authCache: expirable.NewLRU[string, AuthInfo](16, nil, time.Minute), - } - a.authCache.Add("state-x", AuthInfo{Registration: true}) - - got := a.getAuthInfoFromState("state-x") - require.NotNil(t, got) - assert.True(t, got.Registration) - - assert.Nil(t, a.getAuthInfoFromState("state-x"), "a consumed state must not resolve again") -} - -// TestClearOIDCCallbackCookie asserts the cookie is expired (negative MaxAge) on -// the same path it was set with, so the browser drops it. -func TestClearOIDCCallbackCookie(t *testing.T) { - w := httptest.NewRecorder() - clearOIDCCallbackCookie(w, "state_abcdef") - - cookies := w.Result().Cookies() - require.Len(t, cookies, 1) - assert.Equal(t, "state_abcdef", cookies[0].Name) - assert.Negative(t, cookies[0].MaxAge, "deletion cookie must have negative MaxAge") -} - -// TestSetCSRFCookieSecure verifies the Secure flag is driven by the secure -// argument (derived from the configured https server_url), not only req.TLS, so -// cookies stay Secure behind a TLS-terminating reverse proxy where req.TLS is -// nil. -func TestSetCSRFCookieSecure(t *testing.T) { - r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/auth/abcdef0123456789", nil) - - secureRec := httptest.NewRecorder() - setCSRFCookie(secureRec, r, "state", true) - require.Len(t, secureRec.Result().Cookies(), 1) - assert.True(t, secureRec.Result().Cookies()[0].Secure, - "https server_url must set Secure even when req.TLS is nil (proxy case)") - - plainRec := httptest.NewRecorder() - setCSRFCookie(plainRec, r, "state", false) - require.Len(t, plainRec.Result().Cookies(), 1) - assert.False(t, plainRec.Result().Cookies()[0].Secure, - "plain-http server_url without req.TLS must not set Secure") -} diff --git a/hscontrol/platform_config.go b/hscontrol/platform_config.go index 28a918bce..74929ea97 100644 --- a/hscontrol/platform_config.go +++ b/hscontrol/platform_config.go @@ -3,11 +3,12 @@ package hscontrol import ( "bytes" _ "embed" + "html/template" "net/http" textTemplate "text/template" - "github.com/go-chi/chi/v5" "github.com/gofrs/uuid/v5" + "github.com/gorilla/mux" "github.com/juanfont/headscale/hscontrol/templates" ) @@ -35,8 +36,10 @@ func (h *Headscale) ApplePlatformConfig( writer http.ResponseWriter, req *http.Request, ) { - platform := chi.URLParam(req, "platform") - if platform == "" { + vars := mux.Vars(req) + + platform, ok := vars["platform"] + if !ok { httpError(writer, NewHTTPError(http.StatusBadRequest, "no platform specified", nil)) return } @@ -58,17 +61,29 @@ func (h *Headscale) ApplePlatformConfig( URL: h.cfg.ServerURL, } - payloadType, ok := applePayloadType[platform] - if !ok { - httpError(writer, NewHTTPError(http.StatusBadRequest, "platform must be ios, macos-app-store or macos-standalone", nil)) - return - } - - platformConfig.PayloadType = payloadType - var payload bytes.Buffer - if err := payloadTemplate.Execute(&payload, platformConfig); err != nil { //nolint:noinlineerr - httpError(writer, err) + + switch platform { + case "macos-standalone": + err := macosStandaloneTemplate.Execute(&payload, platformConfig) + if err != nil { + httpError(writer, err) + return + } + case "macos-app-store": + err := macosAppStoreTemplate.Execute(&payload, platformConfig) + if err != nil { + httpError(writer, err) + return + } + case "ios": + err := iosTemplate.Execute(&payload, platformConfig) + if err != nil { + httpError(writer, err) + return + } + default: + httpError(writer, NewHTTPError(http.StatusBadRequest, "platform must be ios, macos-app-store or macos-standalone", nil)) return } @@ -97,17 +112,8 @@ type AppleMobileConfig struct { } type AppleMobilePlatformConfig struct { - UUID uuid.UUID - URL string - PayloadType string -} - -// applePayloadType maps a platform request path to the Tailscale IPN -// PayloadType emitted in the rendered Apple profile. -var applePayloadType = map[string]string{ - "ios": "io.tailscale.ipn.ios", - "macos-app-store": "io.tailscale.ipn.macos", - "macos-standalone": "io.tailscale.ipn.macsys", + UUID uuid.UUID + URL string } var commonTemplate = textTemplate.Must( @@ -137,10 +143,10 @@ var commonTemplate = textTemplate.Must( `), ) -var payloadTemplate = textTemplate.Must(textTemplate.New("payloadTemplate").Parse(` +var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(` PayloadType - {{.PayloadType}} + io.tailscale.ipn.ios PayloadUUID {{.UUID}} PayloadIdentifier @@ -154,3 +160,37 @@ var payloadTemplate = textTemplate.Must(textTemplate.New("payloadTemplate").Pars {{.URL}} `)) + +var macosAppStoreTemplate = template.Must(template.New("macosTemplate").Parse(` + + PayloadType + io.tailscale.ipn.macos + PayloadUUID + {{.UUID}} + PayloadIdentifier + com.github.juanfont.headscale + PayloadVersion + 1 + PayloadEnabled + + ControlURL + {{.URL}} + +`)) + +var macosStandaloneTemplate = template.Must(template.New("macosStandaloneTemplate").Parse(` + + PayloadType + io.tailscale.ipn.macsys + PayloadUUID + {{.UUID}} + PayloadIdentifier + com.github.juanfont.headscale + PayloadVersion + 1 + PayloadEnabled + + ControlURL + {{.URL}} + +`)) diff --git a/hscontrol/platform_config_test.go b/hscontrol/platform_config_test.go deleted file mode 100644 index 5d021b142..000000000 --- a/hscontrol/platform_config_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package hscontrol - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-chi/chi/v5" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestApplePlatformConfig_ServesProfilesViaChiRouter is the regression -// guard for issue juanfont/headscale#3296. -// -// The Apple profile download endpoints (`/apple/macos-app-store`, -// `/apple/macos-standalone`, `/apple/ios`) are registered on the chi -// router (see hscontrol/app.go: `r.Get("/apple/{platform}", ...)`). -// Before the fix, `ApplePlatformConfig` extracted the `{platform}` URL -// parameter via `mux.Vars(req)` from gorilla/mux; because the request -// never passed through a gorilla router, the lookup always missed and -// every download returned HTTP 400 `no platform specified`. -// -// This test mounts the route on a chi router exactly as production -// does so the assertion exercises the real router + handler wiring, -// not a hand-crafted chi context. It fails if the handler ever again -// reads URL parameters via an API the production router does not -// populate. -func TestApplePlatformConfig_ServesProfilesViaChiRouter(t *testing.T) { - t.Parallel() - - h := &Headscale{ - cfg: &types.Config{ - ServerURL: "https://headscale.example.com", - }, - } - - // Mirror the production mount in hscontrol/app.go so this test - // covers the actual router + handler wiring, not a hand-crafted - // chi context. - r := chi.NewRouter() - r.Get("/apple/{platform}", h.ApplePlatformConfig) - - srv := httptest.NewServer(r) - t.Cleanup(srv.Close) - - platforms := []string{"macos-app-store", "macos-standalone", "ios"} - for _, platform := range platforms { - t.Run(platform, func(t *testing.T) { - t.Parallel() - - //nolint:noctx // test fixture - resp, err := http.Get(srv.URL + "/apple/" + platform) - require.NoError(t, err) - t.Cleanup(func() { resp.Body.Close() }) - - bodyBytes, _ := io.ReadAll(resp.Body) - body := string(bodyBytes) - - assert.Equal(t, http.StatusOK, resp.StatusCode, - "expected 200 for /apple/%s, got %d: %s", - platform, resp.StatusCode, body) - assert.Equal(t, - "application/x-apple-aspen-config; charset=utf-8", - resp.Header.Get("Content-Type"), - "profile must be served as an Apple aspen config") - assert.Contains(t, body, - "https://headscale.example.com", - "rendered profile must embed the configured ServerURL") - }) - } -} - -// TestApplePlatformConfig_RejectsUnknownPlatform locks the contract for -// the `default:` branch of `ApplePlatformConfig`: an otherwise-valid -// request whose `{platform}` segment is none of the three known values -// must return HTTP 400 with the documented message. This catches both -// silent fallthrough (e.g. a future template registered under a new -// name without adding a case) and accidental message drift. -func TestApplePlatformConfig_RejectsUnknownPlatform(t *testing.T) { - t.Parallel() - - h := &Headscale{ - cfg: &types.Config{ - ServerURL: "https://headscale.example.com", - }, - } - - r := chi.NewRouter() - r.Get("/apple/{platform}", h.ApplePlatformConfig) - - srv := httptest.NewServer(r) - t.Cleanup(srv.Close) - - //nolint:noctx // test fixture - resp, err := http.Get(srv.URL + "/apple/windows-phone") - require.NoError(t, err) - t.Cleanup(func() { resp.Body.Close() }) - - bodyBytes, _ := io.ReadAll(resp.Body) - body := string(bodyBytes) - - assert.Equal(t, http.StatusBadRequest, resp.StatusCode, - "unknown platform must be rejected with 400") - assert.Contains(t, body, - "platform must be ios, macos-app-store or macos-standalone", - "error body must list the supported platforms") -} diff --git a/hscontrol/policy/matcher/export_test.go b/hscontrol/policy/matcher/export_test.go deleted file mode 100644 index 63ec9f09d..000000000 --- a/hscontrol/policy/matcher/export_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package matcher - -// MatchFromStrings builds a [Match] from raw source and destination -// strings. Unparseable entries are silently dropped (fail-open): the -// resulting [Match] is narrower than the input described, but never -// wider. Callers that need strict validation should pre-validate -// their inputs via [util.ParseIPSet]. -func MatchFromStrings(sources, destinations []string) Match { - return Match{ - srcs: buildIPSet(sources), - dests: buildIPSet(destinations), - } -} diff --git a/hscontrol/policy/matcher/matcher.go b/hscontrol/policy/matcher/matcher.go index acfd35b4a..24f2b87a2 100644 --- a/hscontrol/policy/matcher/matcher.go +++ b/hscontrol/policy/matcher/matcher.go @@ -44,16 +44,22 @@ func MatchesFromFilterRules(rules []tailcfg.FilterRule) []Match { return matches } -// MatchFromFilterRule derives a [Match] from a [tailcfg.FilterRule]. The -// destination IP set is the union of [tailcfg.FilterRule.DstPorts][].IP -// and [tailcfg.FilterRule.CapGrant][].Dsts: cap-grant-only rules (e.g. -// tailscale.com/cap/relay) carry their destinations in CapGrant.Dsts and -// would otherwise contribute nothing to peer-visibility derivation in -// [policy.ReduceNodes], hiding the cap target -// from the source unless a companion IP-level rule also exists. +// MatchFromFilterRule derives a Match from a tailcfg.FilterRule. The +// destination IP set is the union of DstPorts[].IP and CapGrant[].Dsts: +// cap-grant-only rules (e.g. tailscale.com/cap/relay) carry their +// destinations in CapGrant.Dsts and would otherwise contribute nothing +// to peer-visibility derivation in BuildPeerMap / ReduceNodes, hiding +// the cap target from the source unless a companion IP-level rule +// also exists. func MatchFromFilterRule(rule tailcfg.FilterRule) Match { + srcs := new(netipx.IPSetBuilder) dests := new(netipx.IPSetBuilder) + for _, srcIP := range rule.SrcIPs { + set, _ := util.ParseIPSet(srcIP, nil) + srcs.AddSet(set) + } + for _, dp := range rule.DstPorts { set, _ := util.ParseIPSet(dp.IP, nil) dests.AddSet(set) @@ -65,29 +71,45 @@ func MatchFromFilterRule(rule tailcfg.FilterRule) Match { } } + srcsSet, _ := srcs.IPSet() destsSet, _ := dests.IPSet() return Match{ - srcs: buildIPSet(rule.SrcIPs), + srcs: srcsSet, dests: destsSet, } } -// buildIPSet parses each string via [util.ParseIPSet] and unions the -// results into a single [netipx.IPSet]. Unparseable entries are silently -// dropped (fail-open): the result is narrower than the input described, -// but never wider. -func buildIPSet(addrs []string) *netipx.IPSet { - builder := new(netipx.IPSetBuilder) +// MatchFromStrings builds a Match from raw source and destination +// strings. Unparseable entries are silently dropped (fail-open): the +// resulting Match is narrower than the input described, but never +// wider. Callers that need strict validation should pre-validate +// their inputs via util.ParseIPSet. +func MatchFromStrings(sources, destinations []string) Match { + srcs := new(netipx.IPSetBuilder) + dests := new(netipx.IPSetBuilder) - for _, addr := range addrs { - set, _ := util.ParseIPSet(addr, nil) - builder.AddSet(set) + for _, srcIP := range sources { + set, _ := util.ParseIPSet(srcIP, nil) + + srcs.AddSet(set) } - set, _ := builder.IPSet() + for _, dest := range destinations { + set, _ := util.ParseIPSet(dest, nil) - return set + dests.AddSet(set) + } + + srcsSet, _ := srcs.IPSet() + destsSet, _ := dests.IPSet() + + match := Match{ + srcs: srcsSet, + dests: destsSet, + } + + return match } func (m *Match) SrcsContainsIPs(ips ...netip.Addr) bool { @@ -109,7 +131,7 @@ func (m *Match) DestsOverlapsPrefixes(prefixes ...netip.Prefix) bool { // DestsIsTheInternet reports whether the destination covers "the // internet" — the set represented by autogroup:internet, special-cased // for exit nodes. Returns true if either family's /0 is contained -// (0.0.0.0/0 or ::/0), or if dests is a superset of [util.TheInternet]. A +// (0.0.0.0/0 or ::/0), or if dests is a superset of TheInternet(). A // single-family /0 counts because operators may write it directly and // it still denotes the whole internet for that family. func (m *Match) DestsIsTheInternet() bool { @@ -118,7 +140,14 @@ func (m *Match) DestsIsTheInternet() bool { return true } - // Superset-of-[util.TheInternet] check handles merged filter rules + // Superset-of-TheInternet check handles merged filter rules // where the internet prefixes are combined with other dests. - return util.IPSetSubsetOf(util.TheInternet(), m.dests) + theInternet := util.TheInternet() + for _, prefix := range theInternet.Prefixes() { + if !m.dests.ContainsPrefix(prefix) { + return false + } + } + + return true } diff --git a/hscontrol/policy/pm.go b/hscontrol/policy/pm.go index ffde6361a..641146d36 100644 --- a/hscontrol/policy/pm.go +++ b/hscontrol/policy/pm.go @@ -33,23 +33,9 @@ type PolicyManager interface { // TagExists reports whether the given tag is defined in the policy. TagExists(tag string) bool - // TagOwnedByTags reports whether a credential holding ownerTags may apply - // tag: true if tag is one of ownerTags, or tag's tag-to-tag ownership chain - // transitively includes one of ownerTags. Authorises the tags an OAuth - // access token may set on the auth keys it mints. - TagOwnedByTags(tag string, ownerTags []string) bool - // NodeCanApproveRoute reports whether the given node can approve the given route. NodeCanApproveRoute(node types.NodeView, route netip.Prefix) bool - // NodeNeedsPeerRecompute reports whether peers must recompute their - // netmap when the node's online state changes. True for subnet - // routers, relay targets (tailscale.com/cap/relay), and via targets; - // false for ordinary nodes, which only need a lightweight online or - // offline peer patch. [State.Connect] and [State.Disconnect] use it to - // avoid a tailnet-wide recompute on every ordinary reconnect. - NodeNeedsPeerRecompute(node types.NodeView) bool - // ViaRoutesForPeer computes via grant effects for a viewer-peer pair. // It returns which routes should be included (peer is via-designated for viewer) // and excluded (steered to a different peer). When no via grants apply, @@ -82,7 +68,7 @@ type PolicyManager interface { DebugString() string } -// NewPolicyManager returns a new [PolicyManager]. +// NewPolicyManager returns a new policy manager. func NewPolicyManager(pol []byte, users []types.User, nodes views.Slice[types.NodeView]) (PolicyManager, error) { var ( polMan PolicyManager @@ -97,8 +83,8 @@ func NewPolicyManager(pol []byte, users []types.User, nodes views.Slice[types.No return polMan, err } -// PolicyManagersForTest returns all available [PolicyManager] implementations to -// be used in tests to validate them in tests that try to determine that they +// PolicyManagersForTest returns all available PostureManagers to be used +// in tests to validate them in tests that try to determine that they // behave the same. func PolicyManagersForTest(pol []byte, users []types.User, nodes views.Slice[types.NodeView]) ([]PolicyManager, error) { var polMans []PolicyManager diff --git a/hscontrol/policy/policy.go b/hscontrol/policy/policy.go index 94316eb72..53cc241e1 100644 --- a/hscontrol/policy/policy.go +++ b/hscontrol/policy/policy.go @@ -8,6 +8,7 @@ import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" "github.com/rs/zerolog/log" + "github.com/samber/lo" "tailscale.com/types/views" ) @@ -49,9 +50,35 @@ func ReduceRoutes( return result } +// BuildPeerMap builds a map of all peers that can be accessed by each node. +func BuildPeerMap( + nodes views.Slice[types.NodeView], + matchers []matcher.Match, +) map[types.NodeID][]types.NodeView { + ret := make(map[types.NodeID][]types.NodeView, nodes.Len()) + + // Build the map of all peers according to the matchers. + // Compared to ReduceNodes, which builds the list per node, we end up with doing + // the full work for every node (On^2), while this will reduce the list as we see + // relationships while building the map, making it O(n^2/2) in the end, but with less work per node. + for i := range nodes.Len() { + for j := i + 1; j < nodes.Len(); j++ { + if nodes.At(i).ID() == nodes.At(j).ID() { + continue + } + + if nodes.At(i).CanAccess(matchers, nodes.At(j)) || nodes.At(j).CanAccess(matchers, nodes.At(i)) { + ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j)) + ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i)) + } + } + } + + return ret +} + // ApproveRoutesWithPolicy checks if the node can approve the announced routes -// and returns the new list of approved routes. The [PolicyManager] is consulted -// via [PolicyManager.NodeCanApproveRoute]. +// and returns the new list of approved routes. // The approved routes will include: // 1. ALL previously approved routes (regardless of whether they're still advertised) // 2. New routes from announcedRoutes that can be auto-approved by policy @@ -65,7 +92,8 @@ func ApproveRoutesWithPolicy(pm PolicyManager, nv types.NodeView, currentApprove } // Start with ALL currently approved routes - we never remove approved routes - newApproved := slices.Clone(currentApproved) + newApproved := make([]netip.Prefix, len(currentApproved)) + copy(newApproved, currentApproved) // Then, check for new routes that can be auto-approved for _, route := range announcedRoutes { @@ -84,12 +112,13 @@ func ApproveRoutesWithPolicy(pm PolicyManager, nv types.NodeView, currentApprove // Sort and deduplicate slices.SortFunc(newApproved, netip.Prefix.Compare) newApproved = slices.Compact(newApproved) - newApproved = slices.DeleteFunc(newApproved, func(route netip.Prefix) bool { - return !route.IsValid() + newApproved = lo.Filter(newApproved, func(route netip.Prefix, index int) bool { + return route.IsValid() }) // Sort the current approved for comparison - sortedCurrent := slices.Clone(currentApproved) + sortedCurrent := make([]netip.Prefix, len(currentApproved)) + copy(sortedCurrent, currentApproved) slices.SortFunc(sortedCurrent, netip.Prefix.Compare) // Only update if the routes actually changed diff --git a/hscontrol/policy/policyutil/doc.go b/hscontrol/policy/policyutil/doc.go index 73a20c907..902a43e45 100644 --- a/hscontrol/policy/policyutil/doc.go +++ b/hscontrol/policy/policyutil/doc.go @@ -1,9 +1,9 @@ // Package policyutil contains pure functions that transform compiled // policy rules for a specific node. The headline function is -// [ReduceFilterRules], which filters global rules down to those relevant +// ReduceFilterRules, which filters global rules down to those relevant // to one node. // -// A node's [types.NodeView.SubnetRoutes] (approved, non-exit) participate -// in rule matching so subnet routers receive filter rules for -// destinations their subnets cover. +// A node's SubnetRoutes (approved, non-exit) participate in rule +// matching so subnet routers receive filter rules for destinations +// their subnets cover — the fix for issue #3169. package policyutil diff --git a/hscontrol/policy/policyutil/reduce.go b/hscontrol/policy/policyutil/reduce.go index fc662aacb..dcd5bee62 100644 --- a/hscontrol/policy/policyutil/reduce.go +++ b/hscontrol/policy/policyutil/reduce.go @@ -6,6 +6,7 @@ import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" + "go4.org/netipx" "tailscale.com/tailcfg" ) @@ -14,8 +15,7 @@ import ( // // IMPORTANT: This function is designed for global filters only. Per-node filters // (from autogroup:self policies) are already node-specific and should not be passed -// to this function. Use [policy.PolicyManager.FilterForNode] instead, which handles -// both cases. +// to this function. Use PolicyManager.FilterForNode() instead, which handles both cases. func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcfg.FilterRule { ret := []tailcfg.FilterRule{} subnetRoutes := node.SubnetRoutes() @@ -49,14 +49,13 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf } // If the node has approved subnet routes, preserve - // filter rules targeting those routes. - // [types.NodeView.SubnetRoutes] returns only approved, - // non-exit routes — matching Tailscale SaaS behavior, - // which does not generate filter rules for - // advertised-but-unapproved routes. Exit routes - // (0.0.0.0/0, ::/0) are excluded by - // [types.NodeView.SubnetRoutes] and handled separately - // via AllowedIPs/routing. + // filter rules targeting those routes. SubnetRoutes() + // returns only approved, non-exit routes — matching + // Tailscale SaaS behavior, which does not generate + // filter rules for advertised-but-unapproved routes. + // Exit routes (0.0.0.0/0, ::/0) are excluded by + // SubnetRoutes() and handled separately via + // AllowedIPs/routing. if slices.ContainsFunc(subnetRoutes, expanded.OverlapsPrefix) { dests = append(dests, dest) continue @@ -65,7 +64,7 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf // Exit-route advertisers need rules targeting the // public internet so the kernel filter accepts // traffic forwarded by autogroup:internet sources. - if hasExitRoutes && util.IPSetSubsetOf(expanded, util.TheInternet()) { + if hasExitRoutes && ipSetSubsetOf(expanded, util.TheInternet()) { dests = append(dests, dest) } } @@ -82,11 +81,25 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf return ret } -// reduceCapGrantRule filters a [tailcfg.CapGrant] rule to only include -// [tailcfg.CapGrant] entries whose Dsts match the given node's IPs. When a -// broad prefix (e.g. 100.64.0.0/10 from dst:*) contains a node's IP, it is +func ipSetSubsetOf(candidate, container *netipx.IPSet) bool { + if candidate == nil || container == nil { + return false + } + + for _, pref := range candidate.Prefixes() { + if !container.ContainsPrefix(pref) { + return false + } + } + + return true +} + +// reduceCapGrantRule filters a CapGrant rule to only include CapGrant +// entries whose Dsts match the given node's IPs. When a broad prefix +// (e.g. 100.64.0.0/10 from dst:*) contains a node's IP, it is // narrowed to the node's specific /32 or /128 prefix. Returns nil if -// no [tailcfg.CapGrant] entries are relevant to this node. +// no CapGrant entries are relevant to this node. func reduceCapGrantRule( node types.NodeView, rule tailcfg.FilterRule, @@ -123,9 +136,9 @@ func reduceCapGrantRule( // prefixes to node-specific /32 or /128 so peers receive only // the minimum routing surface. The route-match loop below // preserves the original prefix so the subnet-serving node - // receives the full CapGrant scope. [types.NodeView.SubnetRoutes] - // excludes both unapproved and exit routes, matching Tailscale - // SaaS behavior. + // receives the full CapGrant scope. SubnetRoutes() excludes + // both unapproved and exit routes, matching Tailscale SaaS + // behavior. for _, dst := range cg.Dsts { for _, subnetRoute := range subnetRoutes { if dst.Overlaps(subnetRoute) { @@ -138,7 +151,7 @@ func reduceCapGrantRule( if len(matchingDsts) > 0 { // A Dst can be appended twice when a broad prefix both // contains a node IP and overlaps one of its approved - // subnet routes. Sort + Compact dedups; [netip.Prefix] is + // subnet routes. Sort + Compact dedups; netip.Prefix is // comparable so Compact works with ==. slices.SortFunc(matchingDsts, netip.Prefix.Compare) matchingDsts = slices.Compact(matchingDsts) diff --git a/hscontrol/policy/v2/canaccess_equiv_test.go b/hscontrol/policy/v2/canaccess_equiv_test.go deleted file mode 100644 index 198ec41ab..000000000 --- a/hscontrol/policy/v2/canaccess_equiv_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package v2 - -import ( - "net/netip" - "testing" - - "github.com/juanfont/headscale/hscontrol/policy/matcher" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "tailscale.com/tailcfg" -) - -// TestCanAccessWithRoutesMatchesCanAccess guards the peer-map optimization: -// CanAccessWithRoutes, fed the same route data CanAccess computes internally, -// must produce identical results. BuildPeerMap relies on this to precompute -// each node's routes once instead of per pair. -func TestCanAccessWithRoutesMatchesCanAccess(t *testing.T) { - user := types.User{Name: "u"} - - subnetRouter := node("subnet", "100.64.0.1", "fd7a:115c:a1e0::1", user) - subnetRouter.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/24")}} - subnetRouter.ApprovedRoutes = []netip.Prefix{netip.MustParsePrefix("10.0.0.0/24")} - - exitNode := node("exit", "100.64.0.2", "fd7a:115c:a1e0::2", user) - exitNode.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")}} - exitNode.ApprovedRoutes = []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")} - - plain := node("plain", "100.64.0.3", "fd7a:115c:a1e0::3", user) - - matchers := matcher.MatchesFromFilterRules([]tailcfg.FilterRule{ - { - SrcIPs: []string{"*"}, - DstPorts: []tailcfg.NetPortRange{{IP: "*", Ports: tailcfg.PortRangeAny}}, - }, - }) - - views := []types.NodeView{subnetRouter.View(), exitNode.View(), plain.View()} - - for _, a := range views { - for _, b := range views { - if a.ID() == b.ID() { - continue - } - - want := a.CanAccess(matchers, b) - got := a.CanAccessWithRoutes(matchers, b, a.SubnetRoutes(), b.SubnetRoutes(), b.IsExitNode()) - assert.Equalf(t, want, got, "%s -> %s", a.Hostname(), b.Hostname()) - } - } -} diff --git a/hscontrol/policy/v2/compiled.go b/hscontrol/policy/v2/compiled.go index 642100c7d..04aca941d 100644 --- a/hscontrol/policy/v2/compiled.go +++ b/hscontrol/policy/v2/compiled.go @@ -11,7 +11,6 @@ import ( "go4.org/netipx" "tailscale.com/tailcfg" "tailscale.com/types/views" - "tailscale.com/util/set" ) // grantCategory classifies a grant by what per-node work it needs. @@ -19,7 +18,7 @@ type grantCategory int const ( // grantCategoryRegular requires no per-node work. The pre-compiled - // rules are complete and only need [policyutil.ReduceFilterRules]. + // rules are complete and only need ReduceFilterRules. grantCategoryRegular grantCategory = iota // grantCategorySelf has autogroup:self destinations that must be @@ -67,57 +66,17 @@ type selfGrantData struct { } // viaGrantData holds data needed for per-node via-grant compilation. -// Sources are already resolved into srcIPStrings; destinations are -// pre-resolved into prefixes plus a flag for autogroup:internet, which -// the consumers handle separately because its gate is per-node -// (IsExitNode()) rather than per-prefix overlap. +// Sources are already resolved into srcIPStrings. type viaGrantData struct { - viaTags []Tag - resolvedDsts []netip.Prefix - hasAutoGroupInternet bool - internetProtocols []ProtocolPort - srcIPStrings []string -} - -// resolveViaDestinations splits a via grant's destinations into the -// flat list of IP prefixes they resolve to plus a flag for -// autogroup:internet. Every alias kind goes through [Alias.Resolve] so -// adding a new alias type to the policy parser does not silently -// disappear from the via path. Non-IP alias kinds (tag, user, group, -// wildcard) resolve to /32 host IPs that never overlap with subnet -// route advertisements and therefore contribute nothing here. -func resolveViaDestinations( - pol *Policy, - users types.Users, - nodes views.Slice[types.NodeView], - dsts Aliases, -) ([]netip.Prefix, bool) { - var ( - prefixes []netip.Prefix - hasAutoGroupInternet bool - ) - - for _, d := range dsts { - if ag, ok := d.(*AutoGroup); ok && ag.Is(AutoGroupInternet) { - hasAutoGroupInternet = true - - continue - } - - ips, err := d.Resolve(pol, users, nodes) - if err != nil || ips == nil { - continue - } - - prefixes = append(prefixes, ips.Prefixes()...) - } - - return prefixes, hasAutoGroupInternet + viaTags []Tag + destinations Aliases + internetProtocols []ProtocolPort + srcIPStrings []string } // userNodeIndex maps user IDs to their untagged nodes. Built once per // policy or node-set change and read from many goroutines under -// [PolicyManager.mu]; readers must hold the lock (or the snapshot +// PolicyManager.mu; readers must hold the lock (or the snapshot // returned to them). type userNodeIndex map[uint][]types.NodeView @@ -137,7 +96,7 @@ func buildUserNodeIndex( } // compileNodeAttrs returns the per-node CapMap derived from policy -// nodeAttrs plus the tailnet-wide [Policy.RandomizeClientPort] flag. +// nodeAttrs plus the tailnet-wide RandomizeClientPort flag. // // Returns an error when a target alias fails to resolve so the caller // surfaces a corrupt policy instead of silently granting a partial set @@ -164,18 +123,18 @@ func (pol *Policy) compileNodeAttrs( result[id] = capMap } - // nil [tailcfg.RawMessage] matches the wire format from a - // Tailscale-hosted control plane: capabilities without companion - // data marshal as null rather than []. Storing nil keeps the - // merge stable and lets the compat test diff cleanly against - // captured netmaps. + // nil RawMessage matches the wire format from a Tailscale-hosted + // control plane: capabilities without companion data marshal as + // `null` rather than `[]`. Storing nil keeps the merge stable + // and lets the compat test diff cleanly against captured + // netmaps. if _, exists := capMap[attr]; !exists { capMap[attr] = nil } } // Cache each node's IPs once per call. Without the cache, the - // node-attr inner loop would call [types.NodeView.IPs] once per attr + // node-attr inner loop would call NodeView.IPs() once per attr // per node — O(grants × nodes) allocations of a 2-element slice // for what is invariant per node within a single policy compile. type nodeIPs struct { @@ -222,11 +181,10 @@ func (pol *Policy) compileNodeAttrs( return result, nil } -// compileGrants resolves all policy grants into [compiledGrant] structs. +// compileGrants resolves all policy grants into compiledGrant structs. // Source resolution and non-self destination resolution happens once // here. This is the single resolution path that replaces the -// duplicated work in [Policy.compileFilterRules] and the autogroup:self -// expansion. +// duplicated work in compileFilterRules and compileGrantWithAutogroupSelf. func (pol *Policy) compileGrants( users types.Users, nodes views.Slice[types.NodeView], @@ -258,7 +216,7 @@ func (pol *Policy) compileGrants( return compiled } -// compileOneGrant resolves a single grant into a [compiledGrant]. +// compileOneGrant resolves a single grant into a compiledGrant. // All source resolution happens here. Non-self, non-via destination // resolution also happens here. Per-node data (self dests, via // matching) is stored for deferred compilation. @@ -325,37 +283,25 @@ func (pol *Policy) compileOneGrant( ) } - // Classify and store deferred self data. The struct literal already - // initializes category to grantCategoryRegular (the zero value). - if len(autogroupSelfDests) > 0 { + // Classify and store deferred self data. + switch { + case len(autogroupSelfDests) > 0: cg.category = grantCategorySelf cg.self = &selfGrantData{ resolvedSrcs: resolvedSrcs, internetProtocols: grant.InternetProtocols, app: grant.App, } + default: + cg.category = grantCategoryRegular } return cg, nil } -// mergeResolvedSrcs merges every prefix from the resolved sources into a -// single [resolved] address set. -func mergeResolvedSrcs(resolvedSrcs []ResolvedAddresses) (resolved, error) { - var b netipx.IPSetBuilder - - for _, ips := range resolvedSrcs { - for _, pref := range ips.Prefixes() { - b.AddPrefix(pref) - } - } - - return newResolved(&b) -} - // compileOneViaGrant resolves sources for a via grant and stores the // deferred per-node data. The actual via-node matching and route -// intersection happens in [compileViaForNode]. +// intersection happens in compileViaForNode. func (pol *Policy) compileOneViaGrant( grant Grant, users types.Users, @@ -377,7 +323,15 @@ func (pol *Policy) compileOneViaGrant( } // Build merged SrcIPs. - srcResolved, err := mergeResolvedSrcs(resolvedSrcs) + var srcIPs netipx.IPSetBuilder + + for _, ips := range resolvedSrcs { + for _, pref := range ips.Prefixes() { + srcIPs.AddPrefix(pref) + } + } + + srcResolved, err := newResolved(&srcIPs) if err != nil { return nil, err } @@ -389,17 +343,12 @@ func (pol *Policy) compileOneViaGrant( hasWildcard := sourcesHaveWildcard(grant.Sources) hasDangerAll := sourcesHaveDangerAll(grant.Sources) - resolvedDsts, hasAutoGroupInternet := resolveViaDestinations( - pol, users, nodes, grant.Destinations, - ) - return &compiledGrant{ category: grantCategoryVia, via: &viaGrantData{ - viaTags: grant.Via, - resolvedDsts: resolvedDsts, - hasAutoGroupInternet: hasAutoGroupInternet, - internetProtocols: grant.InternetProtocols, + viaTags: grant.Via, + destinations: grant.Destinations, + internetProtocols: grant.InternetProtocols, srcIPStrings: srcIPsWithRoutes( srcResolved, hasWildcard, hasDangerAll, nodes, ), @@ -410,8 +359,8 @@ func (pol *Policy) compileOneViaGrant( // resolveSources resolves grant sources per-alias, returning the // resolved addresses and a separate slice of non-wildcard sources. // This is the canonical source-resolution path. Its output lands in -// [compiledGrant.srcIPStrings] (among other places) and callers on the -// hot path should prefer reading that over calling [Alias.Resolve] again. +// compiledGrant.srcIPStrings (among other places) and callers on the +// hot path should prefer reading that over calling Resolve again. func resolveSources( pol *Policy, sources Aliases, @@ -451,8 +400,20 @@ func buildSrcIPStrings( hasWildcard, hasDangerAll bool, nodes views.Slice[types.NodeView], ) []string { - srcResolved, err := mergeResolvedSrcs(resolvedSrcs) - if err != nil || srcResolved.Empty() { + var merged netipx.IPSetBuilder + + for _, ips := range resolvedSrcs { + for _, pref := range ips.Prefixes() { + merged.AddPrefix(pref) + } + } + + srcResolved, err := newResolved(&merged) + if err != nil { + return nil + } + + if srcResolved.Empty() { return nil } @@ -465,12 +426,15 @@ func buildSrcIPStrings( // individual IPs from non-wildcard sources alongside the // merged CGNAT ranges rather than absorbing them. if hasWildcard && len(nonWildcardSrcs) > 0 { - seen := set.SetOf(srcIPStrs) + seen := make(map[string]bool, len(srcIPStrs)) + for _, s := range srcIPStrs { + seen[s] = true + } for _, ips := range nonWildcardSrcs { for _, s := range ips.Strings() { - if !seen.Contains(s) { - seen.Add(s) + if !seen[s] { + seen[s] = true srcIPStrs = append(srcIPStrs, s) } } @@ -481,9 +445,8 @@ func buildSrcIPStrings( } // compileOtherDests compiles filter rules for non-self, non-via -// destinations. This produces both [tailcfg.FilterRule.DstPorts] rules -// (from [Grant.InternetProtocols]) and [tailcfg.CapGrant] rules (from -// [Grant.App]). +// destinations. This produces both DstPorts rules (from +// InternetProtocols) and CapGrant rules (from App). func (pol *Policy) compileOtherDests( users types.Users, nodes views.Slice[types.NodeView], @@ -572,7 +535,7 @@ func (pol *Policy) compileOtherDests( return rules } -// hasPerNodeGrants reports whether any [compiledGrant] requires +// hasPerNodeGrants reports whether any compiled grant requires // per-node filter compilation (via grants or autogroup:self). func hasPerNodeGrants(grants []compiledGrant) bool { for i := range grants { @@ -584,62 +547,10 @@ func hasPerNodeGrants(grants []compiledGrant) bool { return false } -// collectRelayTargetIPs returns the set of IPs that are destinations of a -// tailscale.com/cap/relay grant. A node whose IP is in this set is a relay -// target: when it goes offline, peers holding a PeerRelay allocation through -// it must recompute their netmap to drop the now-dead allocation. The relay -// cap is carried on each grant's [tailcfg.CapGrant] with Dsts set to the -// resolved relay destinations (see [Policy.compileOtherDests]); the reversed -// companion rule carries [tailcfg.PeerCapabilityRelayTarget] instead and is -// intentionally skipped. -func collectRelayTargetIPs(grants []compiledGrant) (*netipx.IPSet, error) { - var b netipx.IPSetBuilder - - for i := range grants { - for _, rule := range grants[i].rules { - for _, cg := range rule.CapGrant { - if _, ok := cg.CapMap[tailcfg.PeerCapabilityRelay]; !ok { - continue - } - - for _, dst := range cg.Dsts { - b.AddPrefix(dst) - } - } - } - } - - return b.IPSet() -} - -// collectViaTargetTags returns the set of tags used as via targets across all -// grants. A node carrying any of these tags is a via target: peers steering -// traffic through it must recompute when it goes offline. Returns nil when no -// via grants exist. -func collectViaTargetTags(grants []compiledGrant) map[Tag]struct{} { - tags := make(map[Tag]struct{}) - - for i := range grants { - if grants[i].via == nil { - continue - } - - for _, t := range grants[i].via.viaTags { - tags[t] = struct{}{} - } - } - - if len(tags) == 0 { - return nil - } - - return tags -} - -// globalFilterRules extracts global filter rules from [compiledGrant]s. -// Via grants produce no global rules (they are per-node only); regular -// grants contribute their full pre-compiled ruleset; self grants -// contribute their non-self portion. +// globalFilterRules extracts global filter rules from compiled +// grants. Via grants produce no global rules (they are per-node +// only); regular grants contribute their full pre-compiled ruleset; +// self grants contribute their non-self portion. func globalFilterRules(grants []compiledGrant) []tailcfg.FilterRule { var rules []tailcfg.FilterRule @@ -848,41 +759,39 @@ func compileViaForNode( return nil } - // [types.NodeView.SubnetRoutes] excludes exit routes, so the overlap - // gate below sees only subnet advertisements. autogroup:internet on - // a via-tagged exit advertiser is handled separately because its - // eligibility is per-node ([types.NodeView.IsExitNode]) rather than - // per-prefix overlap. + // Find matching destination prefixes. SubnetRoutes() excludes exit + // routes, so the *Prefix check below sees only subnet advertisements; + // the *AutoGroup AutoGroupInternet branch checks IsExitNode() instead. nodeSubnetRoutes := node.SubnetRoutes() var viaDstPrefixes []netip.Prefix - for _, dstPrefix := range cg.via.resolvedDsts { - // Equality would reject any broader or narrower dst relative - // to the advertised route. Containment in either direction - // matches the operator's authorisation: a broader dst restricts - // traffic to the subset the router serves; a narrower dst rides - // on a router covering more than the operator asked for. The - // rule emits the literal dst either way because that is what - // the policy authorised. - if slices.ContainsFunc(nodeSubnetRoutes, dstPrefix.Overlaps) { - viaDstPrefixes = append(viaDstPrefixes, dstPrefix) + for _, dst := range cg.via.destinations { + switch d := dst.(type) { + case *Prefix: + dstPrefix := netip.Prefix(*d) + if slices.Contains(nodeSubnetRoutes, dstPrefix) { + viaDstPrefixes = append( + viaDstPrefixes, dstPrefix, + ) + } + case *AutoGroup: + // autogroup:internet on a via-tagged exit advertiser + // becomes a rule whose DstPorts enumerate + // util.TheInternet(). The matchers derived from this + // rule let Node.CanAccess surface the exit node to the + // grant source via DestsIsTheInternet. ReduceFilterRules + // strips the rule from the wire format on non-exit + // advertisers, preserving SaaS PacketFilter encoding. + if d.Is(AutoGroupInternet) && node.IsExitNode() { + viaDstPrefixes = append( + viaDstPrefixes, + util.TheInternet().Prefixes()..., + ) + } } } - // autogroup:internet on a via-tagged exit advertiser becomes a rule - // whose DstPorts enumerate [util.TheInternet]. The matchers derived - // from this rule let [types.NodeView.CanAccess] surface the exit node - // to the grant source via [matcher.Match.DestsIsTheInternet]. - // [policyutil.ReduceFilterRules] strips the rule from the wire format - // on non-exit advertisers, preserving SaaS PacketFilter encoding. - if cg.via.hasAutoGroupInternet && node.IsExitNode() { - viaDstPrefixes = append( - viaDstPrefixes, - util.TheInternet().Prefixes()..., - ) - } - if len(viaDstPrefixes) == 0 { return nil } diff --git a/hscontrol/policy/v2/filter.go b/hscontrol/policy/v2/filter.go index 6eb99707f..7a8dc6284 100644 --- a/hscontrol/policy/v2/filter.go +++ b/hscontrol/policy/v2/filter.go @@ -1,10 +1,12 @@ package v2 import ( + "cmp" "errors" "fmt" "net/netip" "slices" + "strconv" "strings" "time" @@ -21,56 +23,63 @@ var ( errSelfInSources = errors.New("autogroup:self cannot be used in sources") ) -// companionCap pairs a well-known Tailscale capability with its -// companion capability. -type companionCap struct { - original tailcfg.PeerCapability - companion tailcfg.PeerCapability -} - -// companionCaps lists certain well-known Tailscale capabilities and +// companionCaps maps certain well-known Tailscale capabilities to // their companion capability. When a grant includes one of these // capabilities, Tailscale automatically generates an additional -// [tailcfg.FilterRule] with the companion capability and a nil CapMap value. -// The slice is ordered by the original capability name so that -// generated companion rules are emitted deterministically. -var companionCaps = []companionCap{ - {tailcfg.PeerCapabilityTaildrive, tailcfg.PeerCapabilityTaildriveSharer}, - {tailcfg.PeerCapabilityRelay, tailcfg.PeerCapabilityRelayTarget}, +// FilterRule with the companion capability and a nil CapMap value. +var companionCaps = map[tailcfg.PeerCapability]tailcfg.PeerCapability{ + tailcfg.PeerCapabilityTaildrive: tailcfg.PeerCapabilityTaildriveSharer, + tailcfg.PeerCapabilityRelay: tailcfg.PeerCapabilityRelayTarget, } -// companionCapGrantRules returns additional [tailcfg.FilterRule]s for any +// companionCapGrantRules returns additional FilterRules for any // well-known capabilities that have companion caps. Companion rules // are **reversed**: SrcIPs come from the original destinations and // CapGrant Dsts come from the original sources. This allows -// [policyutil.ReduceFilterRules] to distribute companion rules to source -// nodes (e.g. drive-sharer goes to the member nodes, not the destination). +// ReduceFilterRules to distribute companion rules to source nodes +// (e.g. drive-sharer goes to the member nodes, not the destination). // Rules are ordered by the original capability name. // // dstIPStrings are the resolved destination IPs as strings (used as // companion SrcIPs). srcPrefixes are the resolved source IPs as -// [netip.Prefix] (used as companion CapGrant Dsts). +// netip.Prefix (used as companion CapGrant Dsts). func companionCapGrantRules( dstIPStrings []string, srcPrefixes []netip.Prefix, capMap tailcfg.PeerCapMap, ) []tailcfg.FilterRule { - companions := make([]tailcfg.FilterRule, 0, len(companionCaps)) + // Process in deterministic order by original capability name. + type pair struct { + original tailcfg.PeerCapability + companion tailcfg.PeerCapability + } - for _, c := range companionCaps { - if _, ok := capMap[c.original]; ok { - companions = append(companions, tailcfg.FilterRule{ - SrcIPs: dstIPStrings, - CapGrant: []tailcfg.CapGrant{ - { - Dsts: srcPrefixes, - CapMap: tailcfg.PeerCapMap{ - c.companion: nil, - }, + var pairs []pair + + for cap, companion := range companionCaps { + if _, ok := capMap[cap]; ok { + pairs = append(pairs, pair{cap, companion}) + } + } + + slices.SortFunc(pairs, func(a, b pair) int { + return cmp.Compare(a.original, b.original) + }) + + companions := make([]tailcfg.FilterRule, 0, len(pairs)) + + for _, p := range pairs { + companions = append(companions, tailcfg.FilterRule{ + SrcIPs: dstIPStrings, + CapGrant: []tailcfg.CapGrant{ + { + Dsts: srcPrefixes, + CapMap: tailcfg.PeerCapMap{ + p.companion: nil, }, }, - }) - } + }, + }) } return companions @@ -78,7 +87,7 @@ func companionCapGrantRules( // sourcesHaveWildcard returns true if any of the source aliases is // a wildcard (*). Used to determine whether approved subnet routes -// should be appended to [tailcfg.FilterRule.SrcIPs]. +// should be appended to SrcIPs. func sourcesHaveWildcard(srcs Aliases) bool { for _, src := range srcs { if _, ok := src.(Asterix); ok { @@ -90,8 +99,8 @@ func sourcesHaveWildcard(srcs Aliases) bool { } // sourcesHaveDangerAll returns true if any of the source aliases is -// autogroup:danger-all. When present, [tailcfg.FilterRule.SrcIPs] should -// be ["*"] to represent all IP addresses including non-Tailscale addresses. +// autogroup:danger-all. When present, SrcIPs should be ["*"] to +// represent all IP addresses including non-Tailscale addresses. func sourcesHaveDangerAll(srcs Aliases) bool { for _, src := range srcs { if ag, ok := src.(*AutoGroup); ok && ag.Is(AutoGroupDangerAll) { @@ -102,8 +111,8 @@ func sourcesHaveDangerAll(srcs Aliases) bool { return false } -// srcIPsWithRoutes returns the [tailcfg.FilterRule.SrcIPs] string slice, -// appending approved subnet routes when the sources include a wildcard. +// srcIPsWithRoutes returns the SrcIPs string slice, appending +// approved subnet routes when the sources include a wildcard. // When hasDangerAll is true, returns ["*"] to represent all IPs. func srcIPsWithRoutes( resolved ResolvedAddresses, @@ -123,18 +132,17 @@ func srcIPsWithRoutes( return ips } -// compileFilterRules takes a set of nodes and a [Policy] and generates a -// set of Tailscale compatible [tailcfg.FilterRule]s used to allow traffic -// on clients. +// compileFilterRules takes a set of nodes and an ACLPolicy and generates a +// set of Tailscale compatible FilterRules used to allow traffic on clients. func (pol *Policy) compileFilterRules( users types.Users, nodes views.Slice[types.NodeView], -) []tailcfg.FilterRule { +) ([]tailcfg.FilterRule, error) { if pol == nil || (pol.ACLs == nil && pol.Grants == nil) { - return tailcfg.FilterAllowAll + return tailcfg.FilterAllowAll, nil } - return globalFilterRules(pol.compileGrants(users, nodes)) + return globalFilterRules(pol.compileGrants(users, nodes)), nil } func (pol *Policy) destinationsToNetPortRange( @@ -194,15 +202,15 @@ func (pol *Policy) compileFilterRulesForNode( users types.Users, node types.NodeView, nodes views.Slice[types.NodeView], -) []tailcfg.FilterRule { +) ([]tailcfg.FilterRule, error) { if pol == nil { - return tailcfg.FilterAllowAll + return tailcfg.FilterAllowAll, nil } grants := pol.compileGrants(users, nodes) userIdx := buildUserNodeIndex(nodes) - return filterRulesForNode(grants, node, userIdx) + return filterRulesForNode(grants, node, userIdx), nil } var sshAccept = tailcfg.SSHAction{ @@ -213,11 +221,11 @@ var sshAccept = tailcfg.SSHAction{ AllowRemotePortForwarding: true, } -// checkPeriodFromRule extracts the check period duration from an [SSH] rule. -// Returns [SSHCheckPeriodDefault] if no checkPeriod is configured, +// checkPeriodFromRule extracts the check period duration from an SSH rule. +// Returns SSHCheckPeriodDefault if no checkPeriod is configured, // 0 if checkPeriod is "always", or the configured duration otherwise. -// This is used server-side by [PolicyManager.SSHCheckParams] to resolve the -// real period when the client calls back; the wire format always sends 0. +// This is used server-side by SSHCheckParams to resolve the real period +// when the client calls back; the wire format always sends 0. func checkPeriodFromRule(rule SSH) time.Duration { switch { case rule.CheckPeriod == nil: @@ -229,7 +237,7 @@ func checkPeriodFromRule(rule SSH) time.Duration { } } -func sshCheck(baseURL string) tailcfg.SSHAction { +func sshCheck(baseURL string, _ time.Duration) tailcfg.SSHAction { holdURL := baseURL + "/machine/ssh/action/$SRC_NODE_ID/to/$DST_NODE_ID?local_user=$LOCAL_USER" return tailcfg.SSHAction{ @@ -250,7 +258,6 @@ func sshCheck(baseURL string) tailcfg.SSHAction { } } -//nolint:gocyclo // SSH compilation walks per-rule branches with intertwined autogroup:self handling func (pol *Policy) compileSSHPolicy( baseURL string, users types.Users, @@ -293,7 +300,7 @@ func (pol *Policy) compileSSHPolicy( case SSHActionAccept: action = sshAccept case SSHActionCheck: - action = sshCheck(baseURL) + action = sshCheck(baseURL, checkPeriodFromRule(rule)) default: return nil, fmt.Errorf( "parsing SSH policy, unknown action %q, index: %d: %w", @@ -416,7 +423,12 @@ func (pol *Policy) compileSSHPolicy( allPrincipals = append(allPrincipals, taggedPrincipals...) if len(allPrincipals) > 0 { - appendRules(allPrincipals, 0, false) + rules = append(rules, &tailcfg.SSHRule{ + Principals: allPrincipals, + SSHUsers: baseUserMap, + Action: &action, + AcceptEnv: acceptEnv, + }) } } } else if hasLocalpart && slices.ContainsFunc(node.IPs(), srcIPs.Contains) { @@ -465,7 +477,24 @@ func (pol *Policy) compileSSHPolicy( }, nil } -// ipSetToPrincipals converts an [netipx.IPSet] into SSH principals, one per address. +// resolvedAddrsToPrincipals converts ResolvedAddresses into SSH principals, one per address. +func resolvedAddrsToPrincipals(addrs ResolvedAddresses) []*tailcfg.SSHPrincipal { + if addrs == nil { + return nil + } + + var principals []*tailcfg.SSHPrincipal + + for addr := range addrs.Iter() { + principals = append(principals, &tailcfg.SSHPrincipal{ + NodeIP: addr.String(), + }) + } + + return principals +} + +// ipSetToPrincipals converts an IPSet into SSH principals, one per address. func ipSetToPrincipals(ipSet *netipx.IPSet) []*tailcfg.SSHPrincipal { if ipSet == nil { return nil @@ -593,19 +622,36 @@ func groupSourcesByUser( return userIDs, principalsByUser, taggedPrincipals } -// filterRuleKey generates a unique key for merging based on [tailcfg.FilterRule.SrcIPs] -// and [tailcfg.FilterRule.IPProto]. -func filterRuleKey(rule tailcfg.FilterRule) string { - return fmt.Sprintf("%s|%v", strings.Join(rule.SrcIPs, ","), rule.IPProto) +func ipSetToPrefixStringList(ips *netipx.IPSet) []string { + var out []string + + if ips == nil { + return out + } + + for _, pref := range ips.Prefixes() { + out = append(out, pref.String()) + } + + return out } -// mergeFilterRules merges rules with identical [tailcfg.FilterRule.SrcIPs] and -// [tailcfg.FilterRule.IPProto] by combining their [tailcfg.FilterRule.DstPorts]. -// DstPorts are NOT deduplicated to match Tailscale behavior. -// [tailcfg.CapGrant] rules (which have no [tailcfg.FilterRule.DstPorts]) are -// passed through without merging since [tailcfg.CapGrant] and -// [tailcfg.FilterRule.DstPorts] are mutually exclusive in a -// [tailcfg.FilterRule]. +// filterRuleKey generates a unique key for merging based on SrcIPs and IPProto. +func filterRuleKey(rule tailcfg.FilterRule) string { + srcKey := strings.Join(rule.SrcIPs, ",") + + protoStrs := make([]string, len(rule.IPProto)) + for i, p := range rule.IPProto { + protoStrs[i] = strconv.Itoa(p) + } + + return srcKey + "|" + strings.Join(protoStrs, ",") +} + +// mergeFilterRules merges rules with identical SrcIPs and IPProto by combining +// their DstPorts. DstPorts are NOT deduplicated to match Tailscale behavior. +// CapGrant rules (which have no DstPorts) are passed through without merging +// since CapGrant and DstPorts are mutually exclusive in a FilterRule. func mergeFilterRules(rules []tailcfg.FilterRule) []tailcfg.FilterRule { if len(rules) <= 1 { return rules diff --git a/hscontrol/policy/v2/filter_test.go b/hscontrol/policy/v2/filter_test.go index b6afc8e08..81f78f413 100644 --- a/hscontrol/policy/v2/filter_test.go +++ b/hscontrol/policy/v2/filter_test.go @@ -368,7 +368,7 @@ func TestParsing(t *testing.T) { return } - rules := pol.compileFilterRules( + rules, err := pol.compileFilterRules( users, types.Nodes{ &types.Node{ @@ -381,6 +381,12 @@ func TestParsing(t *testing.T) { }, }.ViewSlice()) + if (err != nil) != tt.wantErr { + t.Errorf("parsing() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if diff := cmp.Diff(tt.want, rules); diff != "" { t.Errorf("parsing() unexpected result (-want +got):\n%s", diff) } @@ -1334,7 +1340,10 @@ func TestCompileFilterRulesForNodeWithAutogroupSelf(t *testing.T) { // Test compilation for user1's first node node1 := nodes[0].View() - rules := policy2.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + rules, err := policy2.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } if len(rules) != 1 { t.Fatalf("expected 1 rule, got %d", len(rules)) @@ -1492,7 +1501,8 @@ func TestTagUserMutualExclusivity(t *testing.T) { // matching the production pipeline in filterForNodeLocked. userNode := nodes[0].View() - compiled := pol.compileFilterRulesForNode(users, userNode, nodes.ViewSlice()) + compiled, err := pol.compileFilterRulesForNode(users, userNode, nodes.ViewSlice()) + require.NoError(t, err) userRules := policyutil.ReduceFilterRules(userNode, compiled) @@ -1514,7 +1524,8 @@ func TestTagUserMutualExclusivity(t *testing.T) { // Tag:database should receive the tag:server → tag:database rule after reduction. dbNode := nodes[3].View() - compiled = pol.compileFilterRulesForNode(users, dbNode, nodes.ViewSlice()) + compiled, err = pol.compileFilterRulesForNode(users, dbNode, nodes.ViewSlice()) + require.NoError(t, err) dbRules := policyutil.ReduceFilterRules(dbNode, compiled) @@ -1585,7 +1596,8 @@ func TestUserToTagCrossIdentityGrant(t *testing.T) { // user1's IP as source. taggedNode := nodes[2].View() - compiled := pol.compileFilterRulesForNode(users, taggedNode, nodes.ViewSlice()) + compiled, err := pol.compileFilterRulesForNode(users, taggedNode, nodes.ViewSlice()) + require.NoError(t, err) rules := policyutil.ReduceFilterRules(taggedNode, compiled) @@ -1723,7 +1735,8 @@ func TestAutogroupTagged(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - rules := policy.compileFilterRulesForNode(users, tt.sourceNode, nodes.ViewSlice()) + rules, err := policy.compileFilterRulesForNode(users, tt.sourceNode, nodes.ViewSlice()) + require.NoError(t, err) // Verify all expected destinations are reachable for _, expectedDest := range tt.shouldReach { @@ -1806,7 +1819,8 @@ func TestAutogroupSelfWithSpecificUserSource(t *testing.T) { // For user1's node: sources should be user1's devices node1 := nodes[0].View() - rules := policy.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + rules, err := policy.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + require.NoError(t, err) require.Len(t, rules, 1) expectedSourceIPs := []string{"100.64.0.1", "100.64.0.2"} @@ -1836,7 +1850,8 @@ func TestAutogroupSelfWithSpecificUserSource(t *testing.T) { assert.ElementsMatch(t, expectedDestIPs, actualDestIPs) node2 := nodes[2].View() - rules2 := policy.compileFilterRulesForNode(users, node2, nodes.ViewSlice()) + rules2, err := policy.compileFilterRulesForNode(users, node2, nodes.ViewSlice()) + require.NoError(t, err) assert.Empty(t, rules2, "user2's node should have no rules (user1@ devices can't match user2's self)") } @@ -1878,7 +1893,8 @@ func TestAutogroupSelfWithGroupSource(t *testing.T) { // (group:admins has user1+user2, but autogroup:self filters to same user) node1 := nodes[0].View() - rules := policy.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + rules, err := policy.compileFilterRulesForNode(users, node1, nodes.ViewSlice()) + require.NoError(t, err) require.Len(t, rules, 1) expectedSrcIPs := []string{"100.64.0.1", "100.64.0.2"} @@ -1900,7 +1916,8 @@ func TestAutogroupSelfWithGroupSource(t *testing.T) { } node3 := nodes[4].View() - rules3 := policy.compileFilterRulesForNode(users, node3, nodes.ViewSlice()) + rules3, err := policy.compileFilterRulesForNode(users, node3, nodes.ViewSlice()) + require.NoError(t, err) assert.Empty(t, rules3, "user3 should have no rules") } @@ -2349,7 +2366,8 @@ func TestAutogroupSelfWithNonExistentUserInGroup(t *testing.T) { // Test superadmin's device: should have rules with tag:common, tag:tech, tag:privileged destinations // and superadmin's IP should appear in sources (partial resolution of group:superadmin works) superadminNode := nodes[0].View() - superadminRules := policy.compileFilterRulesForNode(users, superadminNode, nodes.ViewSlice()) + superadminRules, err := policy.compileFilterRulesForNode(users, superadminNode, nodes.ViewSlice()) + require.NoError(t, err) assert.True(t, containsIP(superadminRules, "100.64.0.10"), "rules should include tag:common server") assert.True(t, containsIP(superadminRules, "100.64.0.11"), "rules should include tag:tech server") assert.True(t, containsIP(superadminRules, "100.64.0.12"), "rules should include tag:privileged server") @@ -2365,7 +2383,8 @@ func TestAutogroupSelfWithNonExistentUserInGroup(t *testing.T) { // partial result to be discarded via `continue`. With the fix, superadmin's IPs // from group:superadmin are retained alongside admin's IPs from group:admin. adminNode := nodes[1].View() - adminRules := policy.compileFilterRulesForNode(users, adminNode, nodes.ViewSlice()) + adminRules, err := policy.compileFilterRulesForNode(users, adminNode, nodes.ViewSlice()) + require.NoError(t, err) // Rule 1 sources: [group:superadmin, group:admin, group:direction] // Without fix: group:superadmin discarded -> only admin + direction IPs in sources @@ -2379,7 +2398,8 @@ func TestAutogroupSelfWithNonExistentUserInGroup(t *testing.T) { // Test direction's device: similar to admin, verifies group:direction sources work directionNode := nodes[2].View() - directionRules := policy.compileFilterRulesForNode(users, directionNode, nodes.ViewSlice()) + directionRules, err := policy.compileFilterRulesForNode(users, directionNode, nodes.ViewSlice()) + require.NoError(t, err) assert.True(t, containsIP(directionRules, "100.64.0.10"), "direction rules should include tag:common server") assert.True(t, containsSrcIP(directionRules, "100.64.0.3"), @@ -3573,7 +3593,8 @@ func TestFilterAllowAllFix(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - rules := tt.pol.compileFilterRules(users, nodes) + rules, err := tt.pol.compileFilterRules(users, nodes) + require.NoError(t, err) isFilterAllowAll := cmp.Diff(tailcfg.FilterAllowAll, rules) == "" assert.Equal(t, tt.wantFilterAllow, isFilterAllowAll, diff --git a/hscontrol/policy/v2/issue_3267_test.go b/hscontrol/policy/v2/issue_3267_test.go deleted file mode 100644 index 007ae09e9..000000000 --- a/hscontrol/policy/v2/issue_3267_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package v2 - -import ( - "net/netip" - "slices" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" - "gorm.io/gorm" - "tailscale.com/tailcfg" -) - -const issue3267AliceEmail = "alice@headscale.net" - -// TestIssue3267ViaGrantBroaderDestination locks the SaaS contract for -// a via grant whose destination is a host alias broader than (or -// narrower than) the router's advertised subnet route. Alice's only -// path into the subnet is via tag:subnet-router. The grant destination -// resolves to a /64 (IPv6) or /16 (IPv4), and the router advertises a -// contained /120 / /24. Pre-fix the policy compiler emitted no rule -// and ViaRoutesForPeer left Include empty because the prefix relation -// was checked by slices.Contains (exact equality). SaaS behaviour is -// the authority — see testdata/grant_results/via-grant-v47..v51 for -// the equivalent compatibility regression. -func TestIssue3267ViaGrantBroaderDestination(t *testing.T) { - t.Parallel() - - users := types.Users{ - {Model: gorm.Model{ID: 1}, Name: "alice", Email: issue3267AliceEmail}, //nolint:goconst - } - - cases := []struct { - name string - hostAlias string - dst string // value the hosts alias resolves to - advertised string // narrower prefix the router actually serves - }{ - { - name: "ipv6_4via6_64_dst_with_120_advertised", - hostAlias: "example-4via6", - dst: "fd7a:115c:a1e0:b1a::/64", - advertised: "fd7a:115c:a1e0:b1a:0:13:ad2:7300/120", - }, - { - name: "ipv4_16_dst_with_24_advertised", - hostAlias: "subnet", - dst: "10.33.0.0/16", - advertised: "10.33.5.0/24", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - aliceLaptop := node("alice-laptop", "100.64.0.10", "fd7a:115c:a1e0::a", users[0]) - aliceLaptop.ID = 1 - - router := node("subnet-router", "100.64.0.11", "fd7a:115c:a1e0::b", users[0]) - router.ID = 2 - router.Tags = []string{"tag:subnet-router"} - route := netip.MustParsePrefix(tc.advertised) - router.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{route}} - router.ApprovedRoutes = []netip.Prefix{route} - - nodes := types.Nodes{aliceLaptop, router} - - policy := `{ - "tagOwners": { - "tag:subnet-router": ["` + issue3267AliceEmail + `"] - }, - "hosts": { - "` + tc.hostAlias + `": "` + tc.dst + `" - }, - "grants": [ - { - "src": ["` + issue3267AliceEmail + `"], - "dst": ["` + tc.hostAlias + `"], - "via": ["tag:subnet-router"], - "ip": ["icmp:*"] - } - ] - }` - - pm, err := NewPolicyManager([]byte(policy), users, nodes.ViewSlice()) - require.NoError(t, err) - - pol, err := unmarshalPolicy([]byte(policy)) - require.NoError(t, err) - require.NoError(t, pol.validate()) - - t.Run("compileFilterRulesForNode_emits_rule_with_grant_dst", func(t *testing.T) { - t.Parallel() - - rules := pol.compileFilterRulesForNode(users, router.View(), nodes.ViewSlice()) - - found := slices.ContainsFunc(rules, func(r tailcfg.FilterRule) bool { - return slices.ContainsFunc(r.DstPorts, func(d tailcfg.NetPortRange) bool { - return d.IP == tc.dst - }) - }) - require.Truef(t, found, - "router %s must receive a via filter rule whose DstPorts.IP equals the grant dst %q; got rules=%+v", - router.Hostname, tc.dst, rules) - }) - - t.Run("ViaRoutesForPeer_includes_advertised_prefix", func(t *testing.T) { - t.Parallel() - - result := pm.ViaRoutesForPeer(aliceLaptop.View(), router.View()) - require.Contains(t, result.Include, route, - "alice viewing tag:subnet-router must Include advertised prefix %s — drives AllowedIPs in state.RoutesForPeer", route) - require.Empty(t, result.Exclude, - "alice viewing tag:subnet-router must not Exclude any prefix — there is no competing via tag") - }) - }) - } -} diff --git a/hscontrol/policy/v2/node_recompute_test.go b/hscontrol/policy/v2/node_recompute_test.go deleted file mode 100644 index ed8c9db17..000000000 --- a/hscontrol/policy/v2/node_recompute_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package v2 - -import ( - "net/netip" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" - "gorm.io/gorm" - "tailscale.com/tailcfg" -) - -// TestNodeNeedsPeerRecompute pins which node roles force peers to recompute -// their netmap when the node's online state changes. An ordinary node only -// needs the lightweight online/offline peer patch; subnet routers, relay -// targets, and via targets change what peers compute and therefore need a -// full recompute. The predicate is keyed on the flipping node, so an ordinary -// node in a tailnet that uses relay or via elsewhere must still be classified -// as not needing a recompute. -func TestNodeNeedsPeerRecompute(t *testing.T) { - users := types.Users{ - {Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, - } - - const allowAll = `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - - relayPol := `{ - "tagOwners": {"tag:relay": ["user1@"]}, - "grants": [ - {"src": ["*"], "dst": ["tag:relay"], "app": {"tailscale.com/cap/relay": [{}]}} - ] - }` - - viaPol := `{ - "tagOwners": {"tag:via": ["user1@"]}, - "grants": [ - {"src": ["*"], "dst": ["10.0.0.0/24"], "ip": ["*"], "via": ["tag:via"]} - ] - }` - - taildrivePol := `{ - "tagOwners": {"tag:drive": ["user1@"]}, - "grants": [ - {"src": ["*"], "dst": ["tag:drive"], "app": {"tailscale.com/cap/drive": [{}]}} - ] - }` - - ordinary := node("ordinary", "100.64.0.1", "fd7a:115c:a1e0::1", users[0]) - ordinary.ID = 1 - - subnetRouter := node("subnet", "100.64.0.2", "fd7a:115c:a1e0::2", users[0]) - subnetRouter.ID = 2 - subnetRouter.Hostinfo = &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/24")}, - } - subnetRouter.ApprovedRoutes = []netip.Prefix{netip.MustParsePrefix("10.0.0.0/24")} - - relayTarget := node("relay", "100.64.0.3", "fd7a:115c:a1e0::3", users[0]) - relayTarget.ID = 3 - relayTarget.Tags = []string{"tag:relay"} - - viaTarget := node("via", "100.64.0.4", "fd7a:115c:a1e0::4", users[0]) - viaTarget.ID = 4 - viaTarget.Tags = []string{"tag:via"} - - driveTarget := node("drive", "100.64.0.5", "fd7a:115c:a1e0::5", users[0]) - driveTarget.ID = 5 - driveTarget.Tags = []string{"tag:drive"} - - tests := []struct { - name string - pol string - nodes types.Nodes - subject *types.Node - want bool - }{ - { - name: "ordinary node under allow-all does not need recompute", - pol: allowAll, - nodes: types.Nodes{ordinary}, - subject: ordinary, - want: false, - }, - { - name: "subnet router needs recompute", - pol: allowAll, - nodes: types.Nodes{subnetRouter}, - subject: subnetRouter, - want: true, - }, - { - name: "relay target needs recompute", - pol: relayPol, - nodes: types.Nodes{relayTarget, ordinary}, - subject: relayTarget, - want: true, - }, - { - name: "ordinary node in a relay-using tailnet does not need recompute", - pol: relayPol, - nodes: types.Nodes{relayTarget, ordinary}, - subject: ordinary, - want: false, - }, - { - name: "via target needs recompute", - pol: viaPol, - nodes: types.Nodes{viaTarget, ordinary}, - subject: viaTarget, - want: true, - }, - { - name: "ordinary node in a via-using tailnet does not need recompute", - pol: viaPol, - nodes: types.Nodes{viaTarget, ordinary}, - subject: ordinary, - want: false, - }, - { - name: "taildrive target does not need recompute", - pol: taildrivePol, - nodes: types.Nodes{driveTarget, ordinary}, - subject: driveTarget, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - pm, err := NewPolicyManager([]byte(tt.pol), users, tt.nodes.ViewSlice()) - require.NoError(t, err) - - got := pm.NodeNeedsPeerRecompute(tt.subject.View()) - require.Equal(t, tt.want, got) - }) - } -} diff --git a/hscontrol/policy/v2/policy.go b/hscontrol/policy/v2/policy.go index ae76ad267..a602655f3 100644 --- a/hscontrol/policy/v2/policy.go +++ b/hscontrol/policy/v2/policy.go @@ -15,7 +15,6 @@ import ( "github.com/juanfont/headscale/hscontrol/policy/matcher" "github.com/juanfont/headscale/hscontrol/policy/policyutil" "github.com/juanfont/headscale/hscontrol/types" - "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog/log" "go4.org/netipx" "tailscale.com/net/tsaddr" @@ -25,14 +24,11 @@ import ( "tailscale.com/util/multierr" ) -// ErrInvalidTagOwner is returned when a tag owner is not an [Alias] type. +// ErrInvalidTagOwner is returned when a tag owner is not an Alias type. var ErrInvalidTagOwner = errors.New("tag owner is not an Alias") type PolicyManager struct { - // RWMutex, not Mutex, so concurrent map generation does not serialise on - // reads. The per-node caches are xsync.Maps so a read can fill them without - // taking the write lock. - mu sync.RWMutex + mu sync.Mutex pol *Policy users []types.User nodes views.Slice[types.NodeView] @@ -49,17 +45,8 @@ type PolicyManager struct { autoApproveMapHash deephash.Sum autoApproveMap map[netip.Prefix]*netipx.IPSet - // relayTargetIPs holds the IPs of nodes that are destinations of a - // tailscale.com/cap/relay grant; viaTargetTags holds the tags used as - // via targets. A node matching either, or that is a subnet router, - // forces peers to recompute their netmap when its online state changes - // (see [PolicyManager.NodeNeedsPeerRecompute]). Recomputed from the - // compiled grants on every policy/user/node change. - relayTargetIPs *netipx.IPSet - viaTargetTags map[Tag]struct{} - // Lazy map of SSH policies - sshPolicyMap *xsync.Map[types.NodeID, *tailcfg.SSHPolicy] + sshPolicyMap map[types.NodeID]*tailcfg.SSHPolicy // compiledGrants are the grants with sources pre-resolved. // The single source of truth for filter compilation. Both @@ -68,12 +55,12 @@ type PolicyManager struct { userNodeIdx userNodeIndex // Lazy map of per-node filter rules (reduced, for packet filters) - filterRulesMap *xsync.Map[types.NodeID, []tailcfg.FilterRule] + filterRulesMap map[types.NodeID][]tailcfg.FilterRule // Lazy map of per-node matchers derived from UNREDUCED filter // rules. Only populated on the slow path when needsPerNodeFilter // is true; the fast path returns pm.matchers directly. - matchersForNodeMap *xsync.Map[types.NodeID, []matcher.Match] + matchersForNodeMap map[types.NodeID][]matcher.Match // needsPerNodeFilter is true when any compiled grant requires // per-node work (autogroup:self or via grants). @@ -99,8 +86,8 @@ type filterAndPolicy struct { } // validateUserReferences surfaces ambiguous user@ tokens at policy load so -// duplicate DB rows fail loudly instead of silently dropping rules. -// Missing-user tokens stay tolerant. Empty users → no-op for +// duplicate DB rows fail loudly instead of silently dropping rules (#3160). +// Missing-user tokens stay tolerant (#2863). Empty users → no-op for // syntax-only checks. func validateUserReferences(pol *Policy, users types.Users) error { if pol == nil || len(users) == 0 { @@ -183,7 +170,7 @@ func validateUserReferences(pol *Policy, users types.Users) error { return multierr.New(errs...) } -// NewPolicyManager creates a new [PolicyManager] from a policy file and a list of users and nodes. +// NewPolicyManager creates a new PolicyManager from a policy file and a list of users and nodes. // It returns an error if the policy file is invalid. // The policy manager will update the filter rules based on the users and nodes. func NewPolicyManager(b []byte, users []types.User, nodes views.Slice[types.NodeView]) (*PolicyManager, error) { @@ -201,9 +188,9 @@ func NewPolicyManager(b []byte, users []types.User, nodes views.Slice[types.Node pol: policy, users: users, nodes: nodes, - sshPolicyMap: xsync.NewMap[types.NodeID, *tailcfg.SSHPolicy](), - filterRulesMap: xsync.NewMap[types.NodeID, []tailcfg.FilterRule](), - matchersForNodeMap: xsync.NewMap[types.NodeID, []matcher.Match](), + sshPolicyMap: make(map[types.NodeID]*tailcfg.SSHPolicy, nodes.Len()), + filterRulesMap: make(map[types.NodeID][]tailcfg.FilterRule, nodes.Len()), + matchersForNodeMap: make(map[types.NodeID][]matcher.Match, nodes.Len()), } _, err = pm.updateLocked() @@ -236,14 +223,6 @@ func (pm *PolicyManager) updateLocked() (bool, error) { pm.compiledGrants = pm.pol.compileGrants(pm.users, pm.nodes) pm.userNodeIdx = buildUserNodeIndex(pm.nodes) pm.needsPerNodeFilter = hasPerNodeGrants(pm.compiledGrants) - pm.viaTargetTags = collectViaTargetTags(pm.compiledGrants) - - relayTargetIPs, err := collectRelayTargetIPs(pm.compiledGrants) - if err != nil { - return false, fmt.Errorf("collecting relay target IPs: %w", err) - } - - pm.relayTargetIPs = relayTargetIPs var filter []tailcfg.FilterRule if pm.pol == nil || (pm.pol.ACLs == nil && pm.pol.Grants == nil) { @@ -358,9 +337,9 @@ func (pm *PolicyManager) updateLocked() (bool, error) { // TODO(kradalby): This could potentially be optimized by only clearing the // policies for nodes that have changed. Particularly if the only difference is // that nodes has been added or removed. - pm.sshPolicyMap.Clear() - pm.filterRulesMap.Clear() - pm.matchersForNodeMap.Clear() + clear(pm.sshPolicyMap) + clear(pm.filterRulesMap) + clear(pm.matchersForNodeMap) } // If nothing changed, no need to update nodes @@ -381,55 +360,16 @@ func (pm *PolicyManager) updateLocked() (bool, error) { return true, nil } -// NodeNeedsPeerRecompute reports whether peers must recompute their netmap -// when node's online state changes. A plain node only needs the lightweight -// online/offline peer patch; these roles change what peers compute when the -// node goes up or down, so they require a full recompute: -// - subnet router: primary-route failover changes peers' AllowedIPs -// - relay target (tailscale.com/cap/relay): peers must drop a stale -// PeerRelay allocation -// - via target: peers steer traffic through this node -// -// The check is keyed on the node itself, so an ordinary node in a tailnet -// that uses relay or via for other nodes is correctly classified as not -// needing a recompute. -func (pm *PolicyManager) NodeNeedsPeerRecompute(node types.NodeView) bool { - if !node.Valid() { - return false - } - - // Subnet-router status is intrinsic to the node, so it needs no policy - // state and is checked without the lock. - if node.IsSubnetRouter() { - return true - } - - pm.mu.RLock() - defer pm.mu.RUnlock() - - if pm.relayTargetIPs != nil && node.InIPSet(pm.relayTargetIPs) { - return true - } - - for tag := range pm.viaTargetTags { - if node.HasTag(string(tag)) { - return true - } - } - - return false -} - -// SSHPolicy returns the [tailcfg.SSHPolicy] for node, compiling and +// SSHPolicy returns the tailcfg.SSHPolicy for node, compiling and // caching on first access. Rules use SessionDuration = 0 (no // auto-approval) and emit check URLs of the form // /machine/ssh/action/{src}/to/{dst}?local_user={local_user} per the // SaaS wire format. Cache is invalidated on policy reload. func (pm *PolicyManager) SSHPolicy(baseURL string, node types.NodeView) (*tailcfg.SSHPolicy, error) { - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() - if sshPol, ok := pm.sshPolicyMap.Load(node.ID()); ok { + if sshPol, ok := pm.sshPolicyMap[node.ID()]; ok { return sshPol, nil } @@ -438,7 +378,7 @@ func (pm *PolicyManager) SSHPolicy(baseURL string, node types.NodeView) (*tailcf return nil, fmt.Errorf("compiling SSH policy: %w", err) } - pm.sshPolicyMap.Store(node.ID(), sshPol) + pm.sshPolicyMap[node.ID()] = sshPol return sshPol, nil } @@ -454,8 +394,8 @@ func (pm *PolicyManager) SSHPolicy(baseURL string, node types.NodeView) (*tailcf func (pm *PolicyManager) SSHCheckParams( srcNodeID, dstNodeID types.NodeID, ) (time.Duration, bool) { - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() if pm.pol == nil || len(pm.pol.SSHs) == 0 { return 0, false @@ -502,14 +442,7 @@ func (pm *PolicyManager) SSHCheckParams( // Check if dst node matches any destination. for _, dst := range rule.Destinations { if ag, isAG := dst.(*AutoGroup); isAG && ag.Is(AutoGroupSelf) { - // User().Valid() guards the User().ID() dereference: the - // NodeStore can hold a non-tagged node with UserID set but - // the User association unhydrated (nil), and IsTagged() - // alone does not cover that. Mirrors filter.go's - // autogroup:self guard. Without it, a tailnet client on the - // Noise SSH-check path crashes the server (nil deref). if !srcNode.IsTagged() && !dstNode.IsTagged() && - srcNode.User().Valid() && dstNode.User().Valid() && srcNode.User().ID() == dstNode.User().ID() { return checkPeriodFromRule(rule), true } @@ -588,8 +521,8 @@ func (pm *PolicyManager) Filter() ([]tailcfg.FilterRule, []matcher.Match) { return nil, nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() return pm.filter, pm.matchers } @@ -598,30 +531,13 @@ func (pm *PolicyManager) Filter() ([]tailcfg.FilterRule, []matcher.Match) { // For global filters, it uses the global filter matchers for all nodes. // For autogroup:self policies (empty global filter), it builds per-node // peer maps using each node's specific filter rules. -// -// Compared to [policy.ReduceNodes], which builds the list per node, we end -// up with doing the full work for every node O(n^2), while this will reduce -// the list as we see relationships while building the map, making it -// O(n^2/2) in the end, but with less work per node. func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeView { if pm == nil { return nil } - pm.mu.RLock() - defer pm.mu.RUnlock() - - // Precompute each node's subnet routes and exit-node status once; the - // O(n^2) pair scans below would otherwise recompute them for every pair. - type nodeRoutes struct { - subnet []netip.Prefix - isExit bool - } - - routeInfo := make(map[types.NodeID]nodeRoutes, nodes.Len()) - for _, n := range nodes.All() { - routeInfo[n.ID()] = nodeRoutes{subnet: n.SubnetRoutes(), isExit: n.IsExitNode()} - } + pm.mu.Lock() + defer pm.mu.Unlock() // If we have a global filter, use it for all nodes (normal case). // Via grants require the per-node path because the global filter @@ -630,15 +546,16 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ ret := make(map[types.NodeID][]types.NodeView, nodes.Len()) // Build the map of all peers according to the matchers. + // Compared to ReduceNodes, which builds the list per node, we end up with doing + // the full work for every node O(n^2), while this will reduce the list as we see + // relationships while building the map, making it O(n^2/2) in the end, but with less work per node. for i := range nodes.Len() { for j := i + 1; j < nodes.Len(); j++ { if nodes.At(i).ID() == nodes.At(j).ID() { continue } - ri, rj := routeInfo[nodes.At(i).ID()], routeInfo[nodes.At(j).ID()] - if nodes.At(i).CanAccessWithRoutes(pm.matchers, nodes.At(j), ri.subnet, rj.subnet, rj.isExit) || - nodes.At(j).CanAccessWithRoutes(pm.matchers, nodes.At(i), rj.subnet, ri.subnet, ri.isExit) { + if nodes.At(i).CanAccess(pm.matchers, nodes.At(j)) || nodes.At(j).CanAccess(pm.matchers, nodes.At(i)) { ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j)) ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i)) } @@ -670,12 +587,10 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ for i := range nodes.Len() { nodeI := nodes.At(i) matchersI, hasFilterI := nodeMatchers[nodeI.ID()] - riI := routeInfo[nodeI.ID()] for j := i + 1; j < nodes.Len(); j++ { nodeJ := nodes.At(j) matchersJ, hasFilterJ := nodeMatchers[nodeJ.ID()] - riJ := routeInfo[nodeJ.ID()] // Check all access directions for symmetric peer visibility. // For via grants, filter rules exist on the via-designated node @@ -686,10 +601,10 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ // using nodeI's matchers? (reverse direction: the matchers // on the via node accept traffic FROM the source) // Same for matchersJ in both directions. - canIAccessJ := hasFilterI && nodeI.CanAccessWithRoutes(matchersI, nodeJ, riI.subnet, riJ.subnet, riJ.isExit) - canJAccessI := hasFilterJ && nodeJ.CanAccessWithRoutes(matchersJ, nodeI, riJ.subnet, riI.subnet, riI.isExit) - canJReachI := hasFilterI && nodeJ.CanAccessWithRoutes(matchersI, nodeI, riJ.subnet, riI.subnet, riI.isExit) - canIReachJ := hasFilterJ && nodeI.CanAccessWithRoutes(matchersJ, nodeJ, riI.subnet, riJ.subnet, riJ.isExit) + canIAccessJ := hasFilterI && nodeI.CanAccess(matchersI, nodeJ) + canJAccessI := hasFilterJ && nodeJ.CanAccess(matchersJ, nodeI) + canJReachI := hasFilterI && nodeJ.CanAccess(matchersI, nodeI) + canIReachJ := hasFilterJ && nodeI.CanAccess(matchersJ, nodeJ) if canIAccessJ || canJAccessI || canJReachI || canIReachJ { ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ) @@ -730,7 +645,7 @@ func (pm *PolicyManager) filterForNodeLocked( return nil } - if rules, ok := pm.filterRulesMap.Load(node.ID()); ok { + if rules, ok := pm.filterRulesMap[node.ID()]; ok { return rules } @@ -742,7 +657,7 @@ func (pm *PolicyManager) filterForNodeLocked( } reduced := policyutil.ReduceFilterRules(node, unreduced) - pm.filterRulesMap.Store(node.ID(), reduced) + pm.filterRulesMap[node.ID()] = reduced return reduced } @@ -752,36 +667,36 @@ func (pm *PolicyManager) filterForNodeLocked( // If the policy uses autogroup:self, this returns node-specific compiled rules. // Otherwise, it returns the global filter reduced for this node. // -// Cache is invalidated by [PolicyManager.updateLocked] on policy reload, -// node-set change, or tag-state change. +// Cache is invalidated by updateLocked on policy reload, node-set +// change, or tag-state change. func (pm *PolicyManager) FilterForNode(node types.NodeView) ([]tailcfg.FilterRule, error) { if pm == nil { return nil, nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() return pm.filterForNodeLocked(node), nil } // MatchersForNode returns the matchers for peer relationship determination for a specific node. // These are UNREDUCED matchers - they include all rules where the node could be either source or destination. -// This is different from [PolicyManager.FilterForNode] which returns REDUCED rules for packet filtering. +// This is different from FilterForNode which returns REDUCED rules for packet filtering. // // For global policies: returns the global matchers (same for all nodes) // For autogroup:self: returns node-specific matchers from unreduced compiled rules. // // Per-node results are cached and invalidated on policy/node updates -// so [PolicyManager.BuildPeerMap]'s O(N²) slow path avoids recomputing -// matchers for every pair. +// so BuildPeerMap's O(N²) slow path avoids recomputing matchers for +// every pair. func (pm *PolicyManager) MatchersForNode(node types.NodeView) ([]matcher.Match, error) { if pm == nil { return nil, nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() // For global policies, return the shared global matchers. // Via grants require per-node matchers because the global matchers @@ -790,7 +705,7 @@ func (pm *PolicyManager) MatchersForNode(node types.NodeView) ([]matcher.Match, return pm.matchers, nil } - if cached, ok := pm.matchersForNodeMap.Load(node.ID()); ok { + if cached, ok := pm.matchersForNodeMap[node.ID()]; ok { return cached, nil } @@ -798,7 +713,7 @@ func (pm *PolicyManager) MatchersForNode(node types.NodeView) ([]matcher.Match, // the stored compiled grants for this specific node. unreduced := pm.filterRulesForNodeLocked(node) matchers := matcher.MatchesFromFilterRules(unreduced) - pm.matchersForNodeMap.Store(node.ID(), matchers) + pm.matchersForNodeMap[node.ID()] = matchers return matchers, nil } @@ -817,7 +732,7 @@ func (pm *PolicyManager) SetUsers(users []types.User) (bool, error) { // Clear SSH policy map when users change to force SSH policy recomputation // This ensures that if SSH policy compilation previously failed due to missing users, // it will be retried with the new user list - pm.sshPolicyMap.Clear() + clear(pm.sshPolicyMap) changed, err := pm.updateLocked() if err != nil { @@ -826,7 +741,7 @@ func (pm *PolicyManager) SetUsers(users []types.User) (bool, error) { // If SSH policies exist, force a policy change when users are updated // This ensures nodes get updated SSH policies even if other policy hashes didn't change - if pm.pol != nil && len(pm.pol.SSHs) > 0 { + if pm.pol != nil && pm.pol.SSHs != nil && len(pm.pol.SSHs) > 0 { return true, nil } @@ -870,9 +785,9 @@ func (pm *PolicyManager) SetNodes(nodes views.Slice[types.NodeView]) (bool, erro if !needsUpdate { // This ensures fresh filter rules are generated for all nodes - pm.sshPolicyMap.Clear() - pm.filterRulesMap.Clear() - pm.matchersForNodeMap.Clear() + clear(pm.sshPolicyMap) + clear(pm.filterRulesMap) + clear(pm.matchersForNodeMap) } // Always return true when nodes changed, even if filter hash didn't change // (can happen with autogroup:self or when nodes are added but don't affect rules) @@ -882,23 +797,15 @@ func (pm *PolicyManager) SetNodes(nodes views.Slice[types.NodeView]) (bool, erro return false, nil } -// nodeIDViewMap indexes a slice of node views by node ID. On duplicate IDs the -// last view wins, matching the open-coded loops it replaces. -func nodeIDViewMap(s views.Slice[types.NodeView]) map[types.NodeID]types.NodeView { - m := make(map[types.NodeID]types.NodeView, s.Len()) - for _, n := range s.All() { - m[n.ID()] = n - } - - return m -} - func (pm *PolicyManager) nodesHavePolicyAffectingChanges(newNodes views.Slice[types.NodeView]) bool { if pm.nodes.Len() != newNodes.Len() { return true } - oldNodes := nodeIDViewMap(pm.nodes) + oldNodes := make(map[types.NodeID]types.NodeView, pm.nodes.Len()) + for _, node := range pm.nodes.All() { + oldNodes[node.ID()] = node + } for _, newNode := range newNodes.All() { oldNode, exists := oldNodes[newNode.ID()] @@ -925,22 +832,16 @@ func (pm *PolicyManager) nodesHavePolicyAffectingChanges(newNodes views.Slice[ty // NodeCanHaveTag checks if a node can have the specified tag during client-initiated // registration or reauth flows (e.g., tailscale up --advertise-tags). // -// This function is NOT used by the admin API's [state.State.SetNodeTags] - admins can -// set any existing tag on any node by calling [state.State.SetNodeTags] directly, -// which bypasses this authorization check. +// This function is NOT used by the admin API's SetNodeTags - admins can set any +// existing tag on any node by calling State.SetNodeTags directly, which bypasses +// this authorization check. func (pm *PolicyManager) NodeCanHaveTag(node types.NodeView, tag string) bool { - if pm == nil { + if pm == nil || pm.pol == nil { return false } - pm.mu.RLock() - defer pm.mu.RUnlock() - - // pm.pol is written by SetPolicy under pm.mu; reading it before the - // lock races with concurrent policy reloads. - if pm.pol == nil { - return false - } + pm.mu.Lock() + defer pm.mu.Unlock() // Check if tag exists in policy owners, exists := pm.pol.TagOwners[Tag(tag)] @@ -972,68 +873,8 @@ func (pm *PolicyManager) NodeCanHaveTag(node types.NodeView, tag string) bool { return false } -// TagOwnedByTags reports whether a credential holding ownerTags is authorised to -// apply tag. It is true when tag is one of ownerTags, or when tag's tagOwners -// chain (tag-to-tag ownership) transitively includes one of ownerTags. This is -// the tag-level check used when an OAuth access token mints an auth key: the -// requested tags must each be owned by the token's tags, so an operator token -// tagged tag:k8s-operator may mint tag:k8s keys when the policy declares -// "tag:k8s": ["tag:k8s-operator"]. It is purely tag-relational and does not -// consult node IPs. -func (pm *PolicyManager) TagOwnedByTags(tag string, ownerTags []string) bool { - if pm == nil { - return false - } - - owns := make(map[string]bool, len(ownerTags)) - for _, t := range ownerTags { - owns[t] = true - } - - // A credential may always apply a tag it directly holds; this needs no policy. - if owns[tag] { - return true - } - - pm.mu.RLock() - defer pm.mu.RUnlock() - - // Owned-by delegation requires the policy's tagOwners. - if pm.pol == nil { - return false - } - - // Walk tag-to-tag ownership transitively, guarding against cycles. - visited := make(map[Tag]bool) - - var walk func(t Tag) bool - - walk = func(t Tag) bool { - if visited[t] { - return false - } - - visited[t] = true - - for _, owner := range pm.pol.TagOwners[t] { - ot, ok := owner.(*Tag) - if !ok { - continue - } - - if owns[string(*ot)] || walk(*ot) { - return true - } - } - - return false - } - - return walk(Tag(tag)) -} - // userMatchesOwner checks if a user matches a tag owner entry. -// This is used as a fallback when the node's IP is not in the [PolicyManager.tagOwnerMap]. +// This is used as a fallback when the node's IP is not in the tagOwnerMap. func (pm *PolicyManager) userMatchesOwner(user types.UserView, owner Owner) bool { switch o := owner.(type) { case *Username: @@ -1078,18 +919,12 @@ func (pm *PolicyManager) userMatchesOwner(user types.UserView, owner Owner) bool // TagExists reports whether the given tag is defined in the policy. func (pm *PolicyManager) TagExists(tag string) bool { - if pm == nil { + if pm == nil || pm.pol == nil { return false } - pm.mu.RLock() - defer pm.mu.RUnlock() - - // pm.pol is written by SetPolicy under pm.mu; reading it before the - // lock races with concurrent policy reloads. - if pm.pol == nil { - return false - } + pm.mu.Lock() + defer pm.mu.Unlock() _, exists := pm.pol.TagOwners[Tag(tag)] @@ -1101,8 +936,8 @@ func (pm *PolicyManager) NodeCanApproveRoute(node types.NodeView, route netip.Pr return false } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() // If the route to-be-approved is an exit route, then we need to check // if the node is in allowed to approve it. This is treated differently @@ -1149,29 +984,20 @@ func (pm *PolicyManager) NodeCanApproveRoute(node types.NodeView, route netip.Pr // ViaRoutesForPeer computes via grant effects for a viewer-peer pair. // For each via grant where the viewer matches the source, it checks whether the // peer advertises any of the grant's destination prefixes. If the peer has the -// via tag, those prefixes go into [types.ViaRouteResult.Include]; otherwise -// into [types.ViaRouteResult.Exclude]. +// via tag, those prefixes go into Include; otherwise into Exclude. // -// Performance note: this holds [PolicyManager.mu] for its full duration. Hot -// callers should memoise by (policy-hash, viewer-id) rather than invoking -// this per-pair. -// -//nolint:gocyclo // three-pass via-grant resolution (match, primary election, regular-overlap) +// Performance note: this holds pm.mu for its full duration. Hot +// callers should memoise by (policy-hash, viewer-id) rather than +// invoking this per-pair. func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.ViaRouteResult { var result types.ViaRouteResult - if pm == nil { + if pm == nil || pm.pol == nil { return result } - pm.mu.RLock() - defer pm.mu.RUnlock() - - // pm.pol is written by SetPolicy under pm.mu; reading it before the - // lock races with concurrent policy reloads. - if pm.pol == nil { - return result - } + pm.mu.Lock() + defer pm.mu.Unlock() // Self-steering doesn't apply. if viewer.ID() == peer.ID() { @@ -1183,13 +1009,11 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via grants = append(grants, aclToGrants(acl)...) } - // Resolve each grant's sources against the viewer once, and each - // grant's destinations into a flat prefix list. The three passes - // below reuse both results instead of re-resolving per pass. + // Resolve each grant's sources against the viewer once. The three + // passes below reuse this result instead of calling src.Resolve + // per grant per pass. viewerIPs := viewer.IPs() viewerMatchesGrant := make([]bool, len(grants)) - resolvedDstPrefixes := make([][]netip.Prefix, len(grants)) - grantHasAutoGroupInternet := make([]bool, len(grants)) for i, grant := range grants { for _, src := range grant.Sources { @@ -1204,10 +1028,6 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via break } } - - resolvedDstPrefixes[i], grantHasAutoGroupInternet[i] = resolveViaDestinations( - pm.pol, pm.users, pm.nodes, grant.Destinations, - ) } for i, grant := range grants { @@ -1219,33 +1039,32 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via continue } - // Filter rules and [tailcfg.Node.AllowedIPs] are different layers. - // The filter rule carries the dst (the authorisation surface). - // [tailcfg.Node.AllowedIPs] carries the advertised route (the - // routing fact the viewer needs to pick this peer). This loop - // builds the AllowedIPs side, so it emits routes — not dst - // prefixes. + // Collect destination prefixes that the peer actually advertises. peerSubnetRoutes := peer.SubnetRoutes() var matchedPrefixes []netip.Prefix - for _, dstPrefix := range resolvedDstPrefixes[i] { - for _, route := range peerSubnetRoutes { - if dstPrefix.Overlaps(route) { - matchedPrefixes = append(matchedPrefixes, route) + for _, dst := range grant.Destinations { + switch d := dst.(type) { + case *Prefix: + dstPrefix := netip.Prefix(*d) + if slices.Contains(peerSubnetRoutes, dstPrefix) { + matchedPrefixes = append(matchedPrefixes, dstPrefix) + } + case *AutoGroup: + // Per-viewer steering for autogroup:internet: a peer + // advertising approved exit routes is the via-tagged + // node's analogue of "advertises the destination". + // The downstream Include/Exclude split below restricts + // alice to exit nodes carrying the via tag. + if d.Is(AutoGroupInternet) && peer.IsExitNode() { + matchedPrefixes = append( + matchedPrefixes, peer.ExitRoutes()..., + ) } } } - // Per-viewer steering for autogroup:internet: a peer advertising - // approved exit routes is the via-tagged node's analogue of - // "advertises the destination". The downstream Include/Exclude - // split below restricts the viewer to exit nodes carrying the - // via tag. - if grantHasAutoGroupInternet[i] && peer.IsExitNode() { - matchedPrefixes = append(matchedPrefixes, peer.ExitRoutes()...) - } - if len(matchedPrefixes) == 0 { continue } @@ -1284,11 +1103,11 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via // Include. The others move to Exclude. This mirrors HA // primary election scoped to the via tag group. // - // Unlike the global [tailcfg.Node.PrimaryRoutes] election - // (routes/primary.go), which picks one primary across ALL - // advertisers of a prefix, this election is scoped to the via tag. - // Two via grants with different tags (e.g., tag:ha-a vs tag:ha-b) - // each elect their own winner independently. + // Unlike the global PrimaryRoutes election (routes/primary.go), + // which picks one primary across ALL advertisers of a prefix, + // this election is scoped to the via tag. Two via grants with + // different tags (e.g., tag:ha-a vs tag:ha-b) each elect their + // own winner independently. // // Only process via grants where the viewer matches the source, // otherwise grants for other viewer groups would incorrectly @@ -1302,35 +1121,40 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via continue } - // Elect per matched route, not per dst — a peer can only - // be primary for a prefix it actually advertises, and one - // dst may cover multiple distinct routes. - for _, dstPrefix := range resolvedDstPrefixes[i] { - for _, included := range slices.Clone(result.Include) { - if !dstPrefix.Overlaps(included) { - continue - } + for _, dst := range grant.Destinations { + d, ok := dst.(*Prefix) + if !ok { + continue + } - var viaPrimaryID types.NodeID + dstPrefix := netip.Prefix(*d) + if !slices.Contains(result.Include, dstPrefix) { + continue + } - for _, viaTag := range grant.Via { - for _, node := range pm.nodes.All() { - if node.HasTag(string(viaTag)) && - slices.Contains(node.SubnetRoutes(), included) { - if viaPrimaryID == 0 || node.ID() < viaPrimaryID { - viaPrimaryID = node.ID() - } + // Find the lowest-ID peer with this via tag that + // advertises this prefix — the via-group primary. + var viaPrimaryID types.NodeID + + for _, viaTag := range grant.Via { + for _, node := range pm.nodes.All() { + if node.HasTag(string(viaTag)) && + slices.Contains(node.SubnetRoutes(), dstPrefix) { + if viaPrimaryID == 0 || node.ID() < viaPrimaryID { + viaPrimaryID = node.ID() } } } + } - if viaPrimaryID != 0 && peer.ID() != viaPrimaryID { - result.Include = slices.DeleteFunc(result.Include, func(p netip.Prefix) bool { - return p == included - }) - if !slices.Contains(result.Exclude, included) { - result.Exclude = append(result.Exclude, included) - } + // If the current peer is not the via-group primary, + // demote the prefix from Include to Exclude. + if viaPrimaryID != 0 && peer.ID() != viaPrimaryID { + result.Include = slices.DeleteFunc(result.Include, func(p netip.Prefix) bool { + return p == dstPrefix + }) + if !slices.Contains(result.Exclude, dstPrefix) { + result.Exclude = append(result.Exclude, dstPrefix) } } } @@ -1340,9 +1164,8 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via // When a regular grant also covers a prefix that a via grant // included, defer to global HA primary election (UsePrimary). // When a regular grant covers a prefix that a via grant excluded - // (peer lacks via tag), remove the exclusion so - // [state.State.RoutesForPeer] can apply normal - // [policy.ReduceRoutes] + primary logic. + // (peer lacks via tag), remove the exclusion so RoutesForPeer + // can apply normal ReduceRoutes + primary logic. for i, grant := range grants { if len(grant.Via) > 0 { continue @@ -1352,19 +1175,21 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via continue } - // A non-via grant covering routes that a via grant included - // defers to global HA primary election. Match by overlap so - // a broader or narrower regular dst still catches the - // routes the via grant added to Include. - for _, dstPrefix := range resolvedDstPrefixes[i] { - for _, p := range result.Include { - if dstPrefix.Overlaps(p) && - !slices.Contains(result.UsePrimary, p) { - result.UsePrimary = append(result.UsePrimary, p) + for _, dst := range grant.Destinations { + if d, ok := dst.(*Prefix); ok { + dstPrefix := netip.Prefix(*d) + if slices.Contains(result.Include, dstPrefix) && + !slices.Contains(result.UsePrimary, dstPrefix) { + result.UsePrimary = append(result.UsePrimary, dstPrefix) } - } - result.Exclude = slices.DeleteFunc(result.Exclude, dstPrefix.Overlaps) + // A regular grant overrides a via exclusion: the + // peer doesn't need the via tag if the viewer has + // direct (non-via) access to the prefix. + result.Exclude = slices.DeleteFunc(result.Exclude, func(p netip.Prefix) bool { + return p == dstPrefix + }) + } } } } @@ -1381,11 +1206,6 @@ func (pm *PolicyManager) DebugString() string { return "PolicyManager is not setup" } - // pm.pol, filter, matchers, and the derived maps are all written - // under pm.mu by SetPolicy/SetUsers/SetNodes. - pm.mu.RLock() - defer pm.mu.RUnlock() - var sb strings.Builder fmt.Fprintf(&sb, "PolicyManager (v%d):\n\n", pm.Version()) @@ -1459,25 +1279,27 @@ func (pm *PolicyManager) DebugString() string { // the entire cache. func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.Slice[types.NodeView]) { // Build maps for efficient lookup - oldNodeMap := nodeIDViewMap(oldNodes) - newNodeMap := nodeIDViewMap(newNodes) + oldNodeMap := make(map[types.NodeID]types.NodeView) + for _, node := range oldNodes.All() { + oldNodeMap[node.ID()] = node + } + + newNodeMap := make(map[types.NodeID]types.NodeView) + for _, node := range newNodes.All() { + newNodeMap[node.ID()] = node + } // Track which users are affected by changes. // Tagged nodes don't participate in autogroup:self (identity is tag-based), // so we skip them when collecting affected users, except when tag status changes // (which affects the user's device set). - // - // Ownership is keyed on TypedUserID (the UserID field), not the User - // association view: the NodeStore holds nodes by value with User as a - // *User pointer, and not every write path hydrates that association. A - // non-tagged node always has UserID set, so it is the reliable owner key. - affectedUsers := make(map[types.UserID]struct{}) + affectedUsers := make(map[uint]struct{}) // Check for removed nodes (only non-tagged nodes affect autogroup:self) for nodeID, oldNode := range oldNodeMap { if _, exists := newNodeMap[nodeID]; !exists { if !oldNode.IsTagged() { - affectedUsers[oldNode.TypedUserID()] = struct{}{} + affectedUsers[oldNode.User().ID()] = struct{}{} } } } @@ -1486,7 +1308,7 @@ func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.S for nodeID, newNode := range newNodeMap { if _, exists := oldNodeMap[nodeID]; !exists { if !newNode.IsTagged() { - affectedUsers[newNode.TypedUserID()] = struct{}{} + affectedUsers[newNode.User().ID()] = struct{}{} } } } @@ -1499,10 +1321,10 @@ func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.S if oldNode.IsTagged() != newNode.IsTagged() { if !oldNode.IsTagged() { // Was untagged, now tagged: user lost a device - affectedUsers[oldNode.TypedUserID()] = struct{}{} + affectedUsers[oldNode.User().ID()] = struct{}{} } else { // Was tagged, now untagged: user gained a device - affectedUsers[newNode.TypedUserID()] = struct{}{} + affectedUsers[newNode.User().ID()] = struct{}{} } continue @@ -1514,14 +1336,25 @@ func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.S } // Check if user changed (both versions are non-tagged here) - if oldNode.TypedUserID() != newNode.TypedUserID() { - affectedUsers[oldNode.TypedUserID()] = struct{}{} - affectedUsers[newNode.TypedUserID()] = struct{}{} + if oldNode.User().ID() != newNode.User().ID() { + affectedUsers[oldNode.User().ID()] = struct{}{} + affectedUsers[newNode.User().ID()] = struct{}{} } - // Check if IPs changed. - if !slices.Equal(oldNode.IPs(), newNode.IPs()) { - affectedUsers[newNode.TypedUserID()] = struct{}{} + // Check if IPs changed (simple check - could be more sophisticated) + oldIPs := oldNode.IPs() + + newIPs := newNode.IPs() + if len(oldIPs) != len(newIPs) { + affectedUsers[newNode.User().ID()] = struct{}{} + } else { + // Check if any IPs are different + for i, oldIP := range oldIPs { + if i >= len(newIPs) || oldIP != newIPs[i] { + affectedUsers[newNode.User().ID()] = struct{}{} + break + } + } } } } @@ -1529,41 +1362,63 @@ func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.S // Clear cache entries for affected users only. // For autogroup:self, we need to clear all nodes belonging to affected users // because autogroup:self rules depend on the entire user's device set. - pm.filterRulesMap.Range(func(nodeID types.NodeID, _ []tailcfg.FilterRule) bool { - // Find the user for this cached node using the already-built indexes. - node, ok := newNodeMap[nodeID] - if !ok { - node, ok = oldNodeMap[nodeID] + for nodeID := range pm.filterRulesMap { + // Find the user for this cached node + var nodeUserID uint + + found := false + + // Check in new nodes first + for _, node := range newNodes.All() { + if node.ID() == nodeID { + // Tagged nodes don't participate in autogroup:self, + // so their cache doesn't need user-based invalidation. + if node.IsTagged() { + found = true + break + } + + nodeUserID = node.User().ID() + found = true + + break + } } - // Node not found in either old or new list, clear it. - if !ok { - pm.filterRulesMap.Delete(nodeID) - pm.matchersForNodeMap.Delete(nodeID) + // If not found in new nodes, check old nodes + if !found { + for _, node := range oldNodes.All() { + if node.ID() == nodeID { + if node.IsTagged() { + found = true + break + } - return true + nodeUserID = node.User().ID() + found = true + + break + } + } } - // Tagged nodes don't participate in autogroup:self, so their cache - // doesn't need user-based invalidation; leave nodeUserID at zero. - var nodeUserID types.UserID - if !node.IsTagged() { - nodeUserID = node.TypedUserID() + // If we found the user and they're affected, clear this cache entry + if found { + if _, affected := affectedUsers[nodeUserID]; affected { + delete(pm.filterRulesMap, nodeID) + delete(pm.matchersForNodeMap, nodeID) + } + } else { + // Node not found in either old or new list, clear it + delete(pm.filterRulesMap, nodeID) + delete(pm.matchersForNodeMap, nodeID) } - - // If the owning user is affected, clear this cache entry. - if _, affected := affectedUsers[nodeUserID]; affected { - pm.filterRulesMap.Delete(nodeID) - pm.matchersForNodeMap.Delete(nodeID) - } - - return true - }) + } if len(affectedUsers) > 0 { log.Debug(). Int("affected_users", len(affectedUsers)). - Int("remaining_cache_entries", pm.filterRulesMap.Size()). + Int("remaining_cache_entries", len(pm.filterRulesMap)). Msg("Selectively cleared autogroup:self cache for affected users") } } @@ -1583,10 +1438,17 @@ func (pm *PolicyManager) invalidateNodeCache(newNodes views.Slice[types.NodeView } // invalidateGlobalPolicyCache invalidates only nodes whose properties affecting -// [policyutil.ReduceFilterRules] changed. For global policies, each node's filter is independent. +// ReduceFilterRules changed. For global policies, each node's filter is independent. func (pm *PolicyManager) invalidateGlobalPolicyCache(newNodes views.Slice[types.NodeView]) { - oldNodeMap := nodeIDViewMap(pm.nodes) - newNodeMap := nodeIDViewMap(newNodes) + oldNodeMap := make(map[types.NodeID]types.NodeView) + for _, node := range pm.nodes.All() { + oldNodeMap[node.ID()] = node + } + + newNodeMap := make(map[types.NodeID]types.NodeView) + for _, node := range newNodes.All() { + newNodeMap[node.ID()] = node + } // Invalidate nodes whose properties changed for nodeID, newNode := range newNodeMap { @@ -1597,27 +1459,23 @@ func (pm *PolicyManager) invalidateGlobalPolicyCache(newNodes views.Slice[types. } if newNode.HasNetworkChanges(oldNode) { - pm.filterRulesMap.Delete(nodeID) - pm.matchersForNodeMap.Delete(nodeID) + delete(pm.filterRulesMap, nodeID) + delete(pm.matchersForNodeMap, nodeID) } } // Remove deleted nodes from cache - pm.filterRulesMap.Range(func(nodeID types.NodeID, _ []tailcfg.FilterRule) bool { + for nodeID := range pm.filterRulesMap { if _, exists := newNodeMap[nodeID]; !exists { - pm.filterRulesMap.Delete(nodeID) + delete(pm.filterRulesMap, nodeID) } + } - return true - }) - - pm.matchersForNodeMap.Range(func(nodeID types.NodeID, _ []matcher.Match) bool { + for nodeID := range pm.matchersForNodeMap { if _, exists := newNodeMap[nodeID]; !exists { - pm.matchersForNodeMap.Delete(nodeID) + delete(pm.matchersForNodeMap, nodeID) } - - return true - }) + } } // flattenTags resolves nested tag-owner references. Cycles @@ -1657,8 +1515,8 @@ func flattenTags(tagOwners TagOwners, tag Tag, visiting map[Tag]bool, chain []Ta return result, nil } -// flattenTagOwners flattens all [TagOwners] by resolving nested tags and detecting cycles. -// It will return a new [TagOwners] map where all the [Tag] types have been resolved to their underlying [Owners]. +// flattenTagOwners flattens all TagOwners by resolving nested tags and detecting cycles. +// It will return a new TagOwners map where all the Tag types have been resolved to their underlying Owners. func flattenTagOwners(tagOwners TagOwners) (TagOwners, error) { ret := make(TagOwners) @@ -1679,9 +1537,9 @@ func flattenTagOwners(tagOwners TagOwners) (TagOwners, error) { return ret, nil } -// resolveTagOwners resolves the [TagOwners] to a map of [Tag] to [netipx.IPSet]. -// The resulting map can be used to quickly look up the IPSet for a given [Tag]. -// It is intended for internal use in a [PolicyManager]. +// resolveTagOwners resolves the TagOwners to a map of Tag to netipx.IPSet. +// The resulting map can be used to quickly look up the IPSet for a given Tag. +// It is intended for internal use in a PolicyManager. func resolveTagOwners(p *Policy, users types.Users, nodes views.Slice[types.NodeView]) (map[Tag]*netipx.IPSet, error) { if p == nil { return make(map[Tag]*netipx.IPSet), nil @@ -1799,8 +1657,8 @@ func (pm *PolicyManager) NodeCapMap(id types.NodeID) tailcfg.NodeCapMap { return nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() src := pm.nodeAttrsMap[id] if len(src) == 0 { @@ -1823,8 +1681,8 @@ func (pm *PolicyManager) NodeCapMaps() map[types.NodeID]tailcfg.NodeCapMap { return nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() out := make(map[types.NodeID]tailcfg.NodeCapMap, len(pm.nodeAttrsMap)) maps.Copy(out, pm.nodeAttrsMap) @@ -1833,16 +1691,14 @@ func (pm *PolicyManager) NodeCapMaps() map[types.NodeID]tailcfg.NodeCapMap { } // NodesWithChangedCapMap returns the IDs of nodes whose nodeAttrs -// CapMap shifted across one or more [PolicyManager.updateLocked] calls -// since the last drain. The buffer drains on return. The mapper calls -// this once per [state.State.ReloadPolicy] to decide which nodes need -// a [change.SelfUpdate]. +// CapMap shifted across one or more updateLocked calls since the +// last drain. The buffer drains on return. The mapper calls this +// once per ReloadPolicy to decide which nodes need a SelfUpdate. // -// [PolicyManager.refreshNodeAttrsLocked] APPENDS to the buffer; the drain -// returns the union of every change since the previous read. A concurrent -// [PolicyManager.SetUsers]/[PolicyManager.SetNodes] between -// [PolicyManager.SetPolicy] and a drain cannot silently lose the -// policy-reload diff. +// refreshNodeAttrsLocked APPENDS to the buffer; the drain returns +// the union of every change since the previous read. A concurrent +// SetUsers/SetNodes between SetPolicy and a drain cannot silently +// lose the policy-reload diff. func (pm *PolicyManager) NodesWithChangedCapMap() []types.NodeID { if pm == nil { return nil diff --git a/hscontrol/policy/v2/policy_concurrency_test.go b/hscontrol/policy/v2/policy_concurrency_test.go deleted file mode 100644 index b4b759f59..000000000 --- a/hscontrol/policy/v2/policy_concurrency_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package v2 - -import ( - "fmt" - "sync" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gorm.io/gorm" -) - -// TestPolicyManagerConcurrentReads is the correctness guard for the #3346 fix: -// PolicyManager read methods take a shared RLock and populate their per-node -// caches (filterRulesMap, matchersForNodeMap) concurrently. This test hammers -// those reads from many goroutines while a writer mutates the node set, so the -// race detector catches any unsafe access to the shared caches or policy state. -// -// It uses an autogroup:self policy so reads take the per-node filter slow path -// — the same path that made #3346's reconnect storm expensive — which is where -// the lazy caches are written. -func TestPolicyManagerConcurrentReads(t *testing.T) { - users := types.Users{ - {Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, - {Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, - {Model: gorm.Model{ID: 3}, Name: "user3", Email: "user3@headscale.net"}, - } - - policy := `{ - "acls": [ - { - "action": "accept", - "src": ["autogroup:member"], - "dst": ["autogroup:self:*"] - } - ] - }` - - const nodeCount = 60 - - nodes := make(types.Nodes, 0, nodeCount) - for i := range nodeCount { - n := node( - fmt.Sprintf("node%d", i), - fmt.Sprintf("100.64.0.%d", i+1), - fmt.Sprintf("fd7a:115c:a1e0::%d", i+1), - users[i%len(users)], - ) - n.ID = types.NodeID(i + 1) //nolint:gosec // safe in test - nodes = append(nodes, n) - } - - pm, err := NewPolicyManager([]byte(policy), users, nodes.ViewSlice()) - require.NoError(t, err) - - const ( - readers = 16 - iterations = 60 - mutatorReloads = 30 - ) - - var wg sync.WaitGroup - - // Concurrent readers exercise every converted RLock read path, including - // the two lazily populated per-node caches. Assertions inside the - // goroutines use assert (not require) so a failure does not call - // t.FailNow from a non-test goroutine. - for r := range readers { - wg.Go(func() { - for i := range iterations { - nv := nodes[(r+i)%len(nodes)].View() - - rules, err := pm.FilterForNode(nv) - assert.NoError(t, err) //nolint:testifylint // assert (not require) is correct off the test goroutine - assert.NotNil(t, rules) - - _, err = pm.MatchersForNode(nv) - assert.NoError(t, err) //nolint:testifylint // assert (not require) is correct off the test goroutine - - pm.Filter() - pm.NodeCapMap(nv.ID()) - - // BuildPeerMap is the O(n^2) writer-side read; exercise it - // under RLock too, but not every iteration. - if i%8 == 0 { - assert.NotNil(t, pm.BuildPeerMap(nodes.ViewSlice())) - } - } - }) - } - - // A writer repeatedly re-sets the node set, invalidating and racing the - // caches the readers are populating. - wg.Go(func() { - for range mutatorReloads { - _, err := pm.SetNodes(nodes.ViewSlice()) - assert.NoError(t, err) //nolint:testifylint // assert (not require) is correct off the test goroutine - } - }) - - wg.Wait() -} diff --git a/hscontrol/policy/v2/policy_test.go b/hscontrol/policy/v2/policy_test.go index 6a0d7ada9..20a608486 100644 --- a/hscontrol/policy/v2/policy_test.go +++ b/hscontrol/policy/v2/policy_test.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/juanfont/headscale/hscontrol/policy/matcher" "github.com/juanfont/headscale/hscontrol/types" - "github.com/puzpuzpuz/xsync/v4" "github.com/stretchr/testify/require" "gorm.io/gorm" "tailscale.com/net/tsaddr" @@ -123,7 +122,7 @@ func TestInvalidateAutogroupSelfCache(t *testing.T) { require.NoError(t, err) } - require.Equal(t, len(initialNodes), pm.filterRulesMap.Size()) + require.Len(t, pm.filterRulesMap, len(initialNodes)) tests := []struct { name string @@ -208,154 +207,25 @@ func TestInvalidateAutogroupSelfCache(t *testing.T) { } } - pm.filterRulesMap.Clear() - + pm.filterRulesMap = make(map[types.NodeID][]tailcfg.FilterRule) for _, n := range initialNodes { _, err := pm.FilterForNode(n.View()) require.NoError(t, err) } - initialCacheSize := pm.filterRulesMap.Size() + initialCacheSize := len(pm.filterRulesMap) require.Equal(t, len(initialNodes), initialCacheSize) pm.invalidateAutogroupSelfCache(initialNodes.ViewSlice(), tt.newNodes.ViewSlice()) // Verify the expected number of cache entries were cleared - finalCacheSize := pm.filterRulesMap.Size() + finalCacheSize := len(pm.filterRulesMap) clearedEntries := initialCacheSize - finalCacheSize require.Equal(t, tt.expectedCleared, clearedEntries, tt.description) }) } } -// TestSetNodesAutogroupSelfUnhydratedUser reproduces the panic seen on -// /machine/map when an autogroup:self policy is active and a non-tagged -// node reaches the policy manager with its UserID set but the User -// association left unhydrated (User pointer nil). The NodeStore stores -// nodes by value with User as a *User; not every write path hydrates the -// association, so the autogroup:self cache invalidation must derive the -// owning user from UserID, not from the User view. -func TestSetNodesAutogroupSelfUnhydratedUser(t *testing.T) { - users := types.Users{ - {Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, - {Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, - } - - policy := `{ - "acls": [ - { - "action": "accept", - "src": ["autogroup:member"], - "dst": ["autogroup:self:*"] - } - ] - }` - - // unhydratedNode mirrors a NodeStore snapshot entry whose UserID is - // set (so it is unambiguously user-owned, not tagged) but whose User - // association was never loaded. - unhydratedNode := func(name, ipv4, ipv6 string, userID uint) *types.Node { - return &types.Node{ - Hostname: name, - IPv4: ap(ipv4), - IPv6: ap(ipv6), - UserID: new(userID), - User: nil, - } - } - - initialNodes := types.Nodes{ - node("user1-node1", "100.64.0.1", "fd7a:115c:a1e0::1", users[0]), - node("user2-node1", "100.64.0.2", "fd7a:115c:a1e0::2", users[1]), - } - for i, n := range initialNodes { - n.ID = types.NodeID(i + 1) //nolint:gosec // safe conversion in test - } - - pm, err := NewPolicyManager([]byte(policy), users, initialNodes.ViewSlice()) - require.NoError(t, err) - - require.False(t, initialNodes[0].IsTagged(), "node must be user-owned for autogroup:self") - - // Simulate a node restarting tailscaled: the same node is pushed back - // into the policy manager, but the snapshot version has no hydrated - // User association. This is the exact shape that crashed beta.1. - updatedNodes := types.Nodes{ - unhydratedNode("user1-node1", "100.64.0.1", "fd7a:115c:a1e0::1", users[0].ID), - node("user2-node1", "100.64.0.2", "fd7a:115c:a1e0::2", users[1]), - } - for i, n := range updatedNodes { - n.ID = types.NodeID(i + 1) //nolint:gosec // safe conversion in test - } - - require.NotPanics(t, func() { - _, err = pm.SetNodes(updatedNodes.ViewSlice()) - }, "SetNodes must not panic when a non-tagged node has an unhydrated User") - require.NoError(t, err) -} - -// TestSSHCheckParamsUnhydratedUserNoPanic proves that SSHCheckParams does -// not panic when a non-tagged node reaches the policy manager with its -// UserID set but its User association unhydrated (User pointer nil) — the -// same NodeStore shape that crashed /machine/map in commit 171fd7a3. The -// autogroup:self SSH branch dereferences node.User().ID() guarded only by -// !IsTagged(), not by User().Valid(); SSHCheckParams is reached from the -// Noise SSH-check path (noise.go), so a tailnet client triggers the panic -// and crashes the server (DoS) whenever an SSH check rule with an -// autogroup:self destination is active. -func TestSSHCheckParamsUnhydratedUserNoPanic(t *testing.T) { - users := types.Users{ - {Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, - } - - policy := `{ - "ssh": [ - { - "action": "check", - "src": ["user1@headscale.net"], - "dst": ["autogroup:self"], - "users": ["root"] - } - ] - }` - - initialNodes := types.Nodes{ - node("user1-src", "100.64.0.1", "fd7a:115c:a1e0::1", users[0]), - node("user1-dst", "100.64.0.2", "fd7a:115c:a1e0::2", users[0]), - } - for i, n := range initialNodes { - n.ID = types.NodeID(i + 1) //nolint:gosec // safe conversion in test - } - - pm, err := NewPolicyManager([]byte(policy), users, initialNodes.ViewSlice()) - require.NoError(t, err) - - // Simulate a node restarting tailscaled: the destination node is pushed - // back into the policy manager with no hydrated User association (UserID - // set, User pointer nil), the exact shape that crashed beta.1. - unhydratedDst := &types.Node{ - ID: 2, - Hostname: "user1-dst", - IPv4: ap("100.64.0.2"), - IPv6: ap("fd7a:115c:a1e0::2"), - UserID: new(users[0].ID), - User: nil, - } - require.False(t, unhydratedDst.IsTagged(), "dst node must be user-owned for autogroup:self") - - updatedNodes := types.Nodes{ - node("user1-src", "100.64.0.1", "fd7a:115c:a1e0::1", users[0]), - unhydratedDst, - } - updatedNodes[0].ID = 1 - _, err = pm.SetNodes(updatedNodes.ViewSlice()) - require.NoError(t, err) - - require.NotPanics(t, func() { - pm.SSHCheckParams(types.NodeID(1), types.NodeID(2)) - }, "SSHCheckParams must not panic when a non-tagged node has an unhydrated User") -} - // TestInvalidateGlobalPolicyCache tests the cache invalidation logic for global policies. func TestInvalidateGlobalPolicyCache(t *testing.T) { mustIPPtr := func(s string) *netip.Addr { @@ -500,19 +370,15 @@ func TestInvalidateGlobalPolicyCache(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pm := &PolicyManager{ - nodes: tt.oldNodes.ViewSlice(), - filterRulesMap: xsync.NewMap[types.NodeID, []tailcfg.FilterRule](), - matchersForNodeMap: xsync.NewMap[types.NodeID, []matcher.Match](), - } - for id, rules := range tt.initialCache { - pm.filterRulesMap.Store(id, rules) + nodes: tt.oldNodes.ViewSlice(), + filterRulesMap: tt.initialCache, } pm.invalidateGlobalPolicyCache(tt.newNodes.ViewSlice()) // Verify cache state for nodeID, shouldExist := range tt.expectedCacheAfter { - _, exists := pm.filterRulesMap.Load(nodeID) + _, exists := pm.filterRulesMap[nodeID] require.Equal(t, shouldExist, exists, "node %d cache existence mismatch", nodeID) } }) @@ -869,11 +735,10 @@ func TestTagPropagationToPeerMap(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, matchersForUser2, "MatchersForNode should return non-empty matchers (at least self-access rule)") - // Test [policy.ReduceNodes] logic with the updated nodes and matchers - // This is what [mapper.MapResponseBuilder.buildTailPeers] does - it takes peers from - // [state.State.ListPeers] (which might include user1) and filters them using - // [policy.ReduceNodes] with the updated matchers - // Inline the [policy.ReduceNodes] logic to avoid import cycle + // Test ReduceNodes logic with the updated nodes and matchers + // This is what buildTailPeers does - it takes peers from ListPeers (which might include user1) + // and filters them using ReduceNodes with the updated matchers + // Inline the ReduceNodes logic to avoid import cycle user2View := user2Node.View() user1UpdatedView := user1NodeUpdated.View() @@ -1948,147 +1813,6 @@ func TestViaRoutesForPeer(t *testing.T) { "client should NOT be able to access 10.0.0.0/24 via matchers alone; "+ "state.RoutesForPeer adds via routes after ReduceRoutes to fix this") }) - - t.Run("broader_dst_includes_narrower_advertised_route", func(t *testing.T) { - t.Parallel() - - nodes := types.Nodes{ - { - ID: 1, - Hostname: "viewer", - IPv4: ap("100.64.0.1"), - User: new(users[0]), - UserID: new(users[0].ID), - Hostinfo: &tailcfg.Hostinfo{}, - }, - { - ID: 2, - Hostname: "router", - IPv4: ap("100.64.0.2"), - User: new(users[0]), - UserID: new(users[0].ID), - Tags: []string{"tag:router"}, - Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{mp("10.33.5.0/24")}, - }, - ApprovedRoutes: []netip.Prefix{mp("10.33.5.0/24")}, - }, - } - - pol := `{ - "tagOwners": { - "tag:router": ["user1@"] - }, - "grants": [{ - "src": ["user1@"], - "dst": ["10.0.0.0/8"], - "ip": ["*"], - "via": ["tag:router"] - }] - }` - - pm, err := NewPolicyManager([]byte(pol), users, nodes.ViewSlice()) - require.NoError(t, err) - - result := pm.ViaRoutesForPeer(nodes[0].View(), nodes[1].View()) - require.Equal(t, []netip.Prefix{mp("10.33.5.0/24")}, result.Include, - "Include must hold the advertised route /24, not the broader grant dst /8") - require.Empty(t, result.Exclude) - }) - - t.Run("narrower_dst_includes_advertised_route", func(t *testing.T) { - t.Parallel() - - nodes := types.Nodes{ - { - ID: 1, - Hostname: "viewer", - IPv4: ap("100.64.0.1"), - User: new(users[0]), - UserID: new(users[0].ID), - Hostinfo: &tailcfg.Hostinfo{}, - }, - { - ID: 2, - Hostname: "router", - IPv4: ap("100.64.0.2"), - User: new(users[0]), - UserID: new(users[0].ID), - Tags: []string{"tag:router"}, - Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{mp("10.33.0.0/16")}, - }, - ApprovedRoutes: []netip.Prefix{mp("10.33.0.0/16")}, - }, - } - - pol := `{ - "tagOwners": { - "tag:router": ["user1@"] - }, - "grants": [{ - "src": ["user1@"], - "dst": ["10.33.5.0/24"], - "ip": ["*"], - "via": ["tag:router"] - }] - }` - - pm, err := NewPolicyManager([]byte(pol), users, nodes.ViewSlice()) - require.NoError(t, err) - - result := pm.ViaRoutesForPeer(nodes[0].View(), nodes[1].View()) - require.Equal(t, []netip.Prefix{mp("10.33.0.0/16")}, result.Include, - "Include must hold the advertised route /16 that covers the narrower grant dst /24") - require.Empty(t, result.Exclude) - }) - - t.Run("disjoint_dst_emits_nothing", func(t *testing.T) { - t.Parallel() - - nodes := types.Nodes{ - { - ID: 1, - Hostname: "viewer", - IPv4: ap("100.64.0.1"), - User: new(users[0]), - UserID: new(users[0].ID), - Hostinfo: &tailcfg.Hostinfo{}, - }, - { - ID: 2, - Hostname: "router", - IPv4: ap("100.64.0.2"), - User: new(users[0]), - UserID: new(users[0].ID), - Tags: []string{"tag:router"}, - Hostinfo: &tailcfg.Hostinfo{ - RoutableIPs: []netip.Prefix{mp("10.33.0.0/16")}, - }, - ApprovedRoutes: []netip.Prefix{mp("10.33.0.0/16")}, - }, - } - - pol := `{ - "tagOwners": { - "tag:router": ["user1@"] - }, - "grants": [{ - "src": ["user1@"], - "dst": ["192.168.0.0/16"], - "ip": ["*"], - "via": ["tag:router"] - }] - }` - - pm, err := NewPolicyManager([]byte(pol), users, nodes.ViewSlice()) - require.NoError(t, err) - - result := pm.ViaRoutesForPeer(nodes[0].View(), nodes[1].View()) - require.Empty(t, result.Include, - "disjoint dst must produce nothing — the via gate requires advertised-route overlap") - require.Empty(t, result.Exclude) - }) } // TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible reproduces @@ -2476,75 +2200,3 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) { }) } } - -func TestTagOwnedByTags(t *testing.T) { - // tag:leaf is owned by tag:mid, which is owned by tag:root: a tag-to-tag - // delegation chain, the shape an operator token uses to mint narrower keys. - const policy = `{ - "tagOwners": { - "tag:root": [], - "tag:mid": ["tag:root"], - "tag:leaf": ["tag:mid"], - "tag:lone": [] - }, - "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] - }` - - pm, err := NewPolicyManager([]byte(policy), nil, types.Nodes{}.ViewSlice()) - require.NoError(t, err) - - tests := []struct { - name string - tag string - ownerTags []string - want bool - }{ - { - name: "directly held tag needs no policy", - tag: "tag:lone", - ownerTags: []string{"tag:lone"}, - want: true, - }, - { - name: "one-hop owned-by", - tag: "tag:mid", - ownerTags: []string{"tag:root"}, - want: true, - }, - { - name: "transitive chain root owns leaf", - tag: "tag:leaf", - ownerTags: []string{"tag:root"}, - want: true, - }, - { - name: "owning one link does not grant a sibling", - tag: "tag:lone", - ownerTags: []string{"tag:root"}, - want: false, - }, - { - name: "unowned tag denied", - tag: "tag:leaf", - ownerTags: []string{"tag:unrelated"}, - want: false, - }, - { - name: "empty owners deny a delegated tag", - tag: "tag:leaf", - ownerTags: nil, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, pm.TagOwnedByTags(tt.tag, tt.ownerTags)) - }) - } - - t.Run("nil policy manager denies", func(t *testing.T) { - var nilPM *PolicyManager - require.False(t, nilPM.TagOwnedByTags("tag:leaf", []string{"tag:root"})) - }) -} diff --git a/hscontrol/policy/v2/sshtest.go b/hscontrol/policy/v2/sshtest.go index 89d339c76..f24ce5ff9 100644 --- a/hscontrol/policy/v2/sshtest.go +++ b/hscontrol/policy/v2/sshtest.go @@ -2,7 +2,6 @@ package v2 import ( "fmt" - "maps" "net/netip" "slices" "strings" @@ -23,7 +22,7 @@ import ( // - check: every listed user reaches every dst via a check-action // rule specifically (accept-only matches fail the assertion). -// SSHPolicyTestResult is the outcome of a single [SSHPolicyTest]. +// SSHPolicyTestResult is the outcome of a single SSHPolicyTest. type SSHPolicyTestResult struct { Src string `json:"src"` Passed bool `json:"passed"` @@ -93,7 +92,14 @@ func (r SSHPolicyTestResults) Errors() string { } func sortedUsers(m map[string][]string) []string { - return slices.Sorted(maps.Keys(m)) + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + + slices.Sort(keys) + + return keys } // displayUser shows an empty username as `""` rather than blank. @@ -116,19 +122,23 @@ func checkFailReason(res SSHPolicyTestResult, user, dst string) string { } // RunSSHTests evaluates the live policy's sshTests block and wraps any -// failure in [errSSHPolicyTestsFailed]. +// failure in errSSHPolicyTestsFailed. func (pm *PolicyManager) RunSSHTests() error { if pm == nil || pm.pol == nil || len(pm.pol.SSHTests) == 0 { return nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() cache := make(map[types.NodeID]*tailcfg.SSHPolicy) results := runSSHPolicyTests(pm.pol, pm.users, pm.nodes, cache) - return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results.Errors) + if results.AllPassed { + return nil + } + + return fmt.Errorf("%w:\n%s", errSSHPolicyTestsFailed, results.Errors()) } // evaluateSSHTests runs the block against pol without mutating live state. @@ -144,11 +154,15 @@ func evaluateSSHTests( cache := make(map[types.NodeID]*tailcfg.SSHPolicy) results := runSSHPolicyTests(pol, users, nodes, cache) - return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results.Errors) + if results.AllPassed { + return nil + } + + return fmt.Errorf("%w:\n%s", errSSHPolicyTestsFailed, results.Errors()) } // runSSHPolicyTests evaluates every sshTests entry. The cache is keyed -// by dst [types.NodeID] so repeat destinations only compile once per pass. +// by dst NodeID so repeat destinations only compile once per pass. func runSSHPolicyTests( pol *Policy, users []types.User, @@ -237,21 +251,28 @@ func runSSHPolicyTest( return res } - for _, g := range []struct { - users []SSHUser - kind sshAssertion - }{ - {test.Accept, assertAccept}, - {test.Deny, assertDeny}, - {test.Check, assertCheck}, - } { - for _, user := range g.users { - evaluateAssertion( - pol, users, nodes, cache, - srcAddrs, dstNodes, user.String(), - g.kind, &res, - ) - } + for _, user := range test.Accept { + evaluateAssertion( + pol, users, nodes, cache, + srcAddrs, dstNodes, user.String(), + assertAccept, &res, + ) + } + + for _, user := range test.Deny { + evaluateAssertion( + pol, users, nodes, cache, + srcAddrs, dstNodes, user.String(), + assertDeny, &res, + ) + } + + for _, user := range test.Check { + evaluateAssertion( + pol, users, nodes, cache, + srcAddrs, dstNodes, user.String(), + assertCheck, &res, + ) } return res @@ -368,7 +389,7 @@ func appendUserDst(m map[string][]string, user, dst string) map[string][]string // resolveSSHTestSource returns the src's principal addresses and, for // user-shaped sources, the user ID (so autogroup:self can scope to it). -// [Tag], [Host], and IP sources return userID 0. +// Tag, host, and IP sources return userID 0. func resolveSSHTestSource( src Alias, pol *Policy, @@ -388,7 +409,10 @@ func resolveSSHTestSource( return nil, 0, nil } - out := slices.Collect(addrs.Iter()) + out := make([]netip.Addr, 0) + for a := range addrs.Iter() { + out = append(out, a) + } var userID uint @@ -404,10 +428,9 @@ func resolveSSHTestSource( } // resolveSSHTestDestNodes maps each dst alias to its destination -// [types.NodeView]s. autogroup:self needs special handling: it cannot -// resolve without per-node context, so it walks the node set keyed on -// src's owning user. Other aliases resolve to an [netipx.IPSet] and match -// via [types.NodeView.InIPSet]. +// NodeViews. autogroup:self needs special handling: it cannot resolve +// without per-node context, so it walks the node set keyed on src's +// owning user. Other aliases resolve to an IPSet and match via InIPSet. func resolveSSHTestDestNodes( dsts SSHTestDestinations, pol *Policy, @@ -504,8 +527,8 @@ func resolveSSHTestDestNodes( return out, emptyDsts, nil } -// prefixesToIPSet builds the [netipx.IPSet] that [types.NodeView.InIPSet] -// expects on the node side. +// prefixesToIPSet builds the IPSet that InIPSet expects on the node +// side. func prefixesToIPSet(prefixes []netip.Prefix) (*netipx.IPSet, error) { var b netipx.IPSetBuilder @@ -516,9 +539,9 @@ func prefixesToIPSet(prefixes []netip.Prefix) (*netipx.IPSet, error) { return b.IPSet() } -// compiledSSHPolicy returns the per-node compiled [tailcfg.SSHPolicy], caching +// compiledSSHPolicy returns the per-node compiled SSH policy, caching // on miss. baseURL is empty because reachability only checks for the -// presence of [tailcfg.SSHAction.HoldAndDelegate], not its value. +// presence of HoldAndDelegate, not its value. func compiledSSHPolicy( pol *Policy, users []types.User, @@ -584,8 +607,8 @@ func reachability( return acceptHit, checkHit } -// principalContainsAddr reports whether any principal's [tailcfg.SSHPrincipal.NodeIP] -// matches srcAddr exactly (the SSH compiler emits one principal per source IP). +// principalContainsAddr reports whether any principal's NodeIP matches +// srcAddr exactly (the SSH compiler emits one principal per source IP). func principalContainsAddr( principals []*tailcfg.SSHPrincipal, srcAddr netip.Addr, @@ -612,7 +635,7 @@ func principalContainsAddr( return false } -// sshUserMapAllows reports whether [SSHUsers] permits user. The [SSHUsers] +// sshUserMapAllows reports whether SSHUsers permits user. The SSHUsers // wire shape (see filter.go compileSSHPolicy): // // - SSHUsers["root"] == "root" allows root; == "" disallows it. diff --git a/hscontrol/policy/v2/tailnet_state_caps.go b/hscontrol/policy/v2/tailnet_state_caps.go index 9ee11bf60..cc3fdf8e8 100644 --- a/hscontrol/policy/v2/tailnet_state_caps.go +++ b/hscontrol/policy/v2/tailnet_state_caps.go @@ -4,7 +4,7 @@ package v2 // Tailscale-hosted control plane emits where headscale has no // equivalent concept yet. The compat test in // tailscale_nodeattrs_compat_test.go builds the self-view CapMap via -// [types.Node.TailNode] -- the same call the mapper makes -- and +// [types.NodeView.TailNode] -- the same call the mapper makes -- and // strips these from BOTH sides before [cmp.Diff]; every other cap is // compared in full as it lands on the wire. // @@ -13,7 +13,6 @@ package v2 // one exists. import ( - "maps" "slices" "strings" @@ -31,9 +30,8 @@ import ( // (suggest-exit-node, dns-subdomain-resolve — see // ipn/ipnlocal/local.go:7534 and node_backend.go:745) are emitted only // when the peer satisfies the cap's emission condition. This function -// encodes those conditions; the mapper calls it from -// [mapper.MapResponseBuilder.buildTailPeers] and the compat test calls -// it to compute the expected per-peer wire shape. +// encodes those conditions; the mapper calls it from buildTailPeers and +// the compat test calls it to compute the expected per-peer wire shape. func PeerCapMap(peer types.NodeView, peerSelfCaps tailcfg.NodeCapMap) tailcfg.NodeCapMap { if len(peerSelfCaps) == 0 { return nil @@ -171,10 +169,15 @@ func stripUnmodelledTailnetStateCaps(cm tailcfg.NodeCapMap) tailcfg.NodeCapMap { return nil } - out := maps.Clone(cm) - maps.DeleteFunc(out, func(k tailcfg.NodeCapability, _ []tailcfg.RawMessage) bool { - return isUnmodelledTailnetStateCap(k) - }) + out := make(tailcfg.NodeCapMap, len(cm)) + + for k, v := range cm { + if isUnmodelledTailnetStateCap(k) { + continue + } + + out[k] = v + } if len(out) == 0 { return nil diff --git a/hscontrol/policy/v2/tailscale_acl_data_compat_test.go b/hscontrol/policy/v2/tailscale_acl_data_compat_test.go index ecf212947..7b0be7096 100644 --- a/hscontrol/policy/v2/tailscale_acl_data_compat_test.go +++ b/hscontrol/policy/v2/tailscale_acl_data_compat_test.go @@ -51,6 +51,60 @@ func setupACLCompatUsers() types.Users { } } +// setupACLCompatNodes returns the 8 test nodes for ACL compatibility tests. +// Node GivenNames match the anonymized pokémon naming. +func setupACLCompatNodes(users types.Users) types.Nodes { + return types.Nodes{ + { + ID: 1, GivenName: "bulbasaur", + User: &users[0], UserID: &users[0].ID, + IPv4: ptrAddr("100.90.199.68"), IPv6: ptrAddr("fd7a:115c:a1e0::2d01:c747"), + Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 2, GivenName: "ivysaur", + User: &users[1], UserID: &users[1].ID, + IPv4: ptrAddr("100.110.121.96"), IPv6: ptrAddr("fd7a:115c:a1e0::1737:7960"), + Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 3, GivenName: "venusaur", + User: &users[2], UserID: &users[2].ID, + IPv4: ptrAddr("100.103.90.82"), IPv6: ptrAddr("fd7a:115c:a1e0::9e37:5a52"), + Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 4, GivenName: "beedrill", + IPv4: ptrAddr("100.108.74.26"), IPv6: ptrAddr("fd7a:115c:a1e0::b901:4a87"), + Tags: []string{"tag:server"}, Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 5, GivenName: "kakuna", + IPv4: ptrAddr("100.103.8.15"), IPv6: ptrAddr("fd7a:115c:a1e0::5b37:80f"), + Tags: []string{"tag:prod"}, Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 6, GivenName: "weedle", + IPv4: ptrAddr("100.83.200.69"), IPv6: ptrAddr("fd7a:115c:a1e0::c537:c845"), + Tags: []string{"tag:client"}, Hostinfo: &tailcfg.Hostinfo{}, + }, + { + ID: 7, GivenName: "squirtle", + IPv4: ptrAddr("100.92.142.61"), IPv6: ptrAddr("fd7a:115c:a1e0::3e37:8e3d"), + Tags: []string{"tag:router"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")}, + }, + ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")}, + }, + { + ID: 8, GivenName: "charmander", + IPv4: ptrAddr("100.85.66.106"), IPv6: ptrAddr("fd7a:115c:a1e0::7c37:426a"), + Tags: []string{"tag:exit"}, Hostinfo: &tailcfg.Hostinfo{}, + }, + } +} + // findNodeByGivenName finds a node by its GivenName field. func findNodeByGivenName(nodes types.Nodes, name string) *types.Node { for _, n := range nodes { @@ -357,11 +411,18 @@ func testACLSuccess( } // Compile headscale filter rules for this node - compiledRules := pol.compileFilterRulesForNode( + compiledRules, err := pol.compileFilterRulesForNode( users, node.View(), nodes.ViewSlice(), ) + require.NoError( + t, + err, + "%s/%s: failed to compile filter rules", + tf.TestID, + nodeName, + ) gotRules := policyutil.ReduceFilterRules( node.View(), diff --git a/hscontrol/policy/v2/tailscale_grants_compat_test.go b/hscontrol/policy/v2/tailscale_grants_compat_test.go index b23b14ffc..e2e3e8fbf 100644 --- a/hscontrol/policy/v2/tailscale_grants_compat_test.go +++ b/hscontrol/policy/v2/tailscale_grants_compat_test.go @@ -45,6 +45,218 @@ func setupGrantsCompatUsers() types.Users { } } +// setupGrantsCompatNodes returns the 15 test nodes for grants compatibility tests. +// The node configuration matches the Tailscale test environment: +// - 3 user-owned nodes (bulbasaur, ivysaur, venusaur) +// - 12 tagged nodes (beedrill, kakuna, weedle, squirtle, charmander, +// pidgey, pidgeotto, rattata, raticate, spearow, fearow, blastoise) +func setupGrantsCompatNodes(users types.Users) types.Nodes { + nodeBulbasaur := &types.Node{ + ID: 1, + GivenName: "bulbasaur", + User: &users[0], + UserID: &users[0].ID, + IPv4: ptrAddr("100.90.199.68"), + IPv6: ptrAddr("fd7a:115c:a1e0::2d01:c747"), + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeIvysaur := &types.Node{ + ID: 2, + GivenName: "ivysaur", + User: &users[1], + UserID: &users[1].ID, + IPv4: ptrAddr("100.110.121.96"), + IPv6: ptrAddr("fd7a:115c:a1e0::1737:7960"), + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeVenusaur := &types.Node{ + ID: 3, + GivenName: "venusaur", + User: &users[2], + UserID: &users[2].ID, + IPv4: ptrAddr("100.103.90.82"), + IPv6: ptrAddr("fd7a:115c:a1e0::9e37:5a52"), + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeBeedrill := &types.Node{ + ID: 4, + GivenName: "beedrill", + IPv4: ptrAddr("100.108.74.26"), + IPv6: ptrAddr("fd7a:115c:a1e0::b901:4a87"), + Tags: []string{"tag:server"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeKakuna := &types.Node{ + ID: 5, + GivenName: "kakuna", + IPv4: ptrAddr("100.103.8.15"), + IPv6: ptrAddr("fd7a:115c:a1e0::5b37:80f"), + Tags: []string{"tag:prod"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeWeedle := &types.Node{ + ID: 6, + GivenName: "weedle", + IPv4: ptrAddr("100.83.200.69"), + IPv6: ptrAddr("fd7a:115c:a1e0::c537:c845"), + Tags: []string{"tag:client"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeSquirtle := &types.Node{ + ID: 7, + GivenName: "squirtle", + IPv4: ptrAddr("100.92.142.61"), + IPv6: ptrAddr("fd7a:115c:a1e0::3e37:8e3d"), + Tags: []string{"tag:router"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")}, + }, + ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")}, + } + + nodeCharmander := &types.Node{ + ID: 8, + GivenName: "charmander", + IPv4: ptrAddr("100.85.66.106"), + IPv6: ptrAddr("fd7a:115c:a1e0::7c37:426a"), + Tags: []string{"tag:exit"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + ApprovedRoutes: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + } + + // --- New nodes for expanded via grant topology --- + + nodePidgey := &types.Node{ + ID: 9, + GivenName: "pidgey", + IPv4: ptrAddr("100.124.195.93"), + IPv6: ptrAddr("fd7a:115c:a1e0::7837:c35d"), + Tags: []string{"tag:exit-a"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + ApprovedRoutes: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + } + + nodePidgeotto := &types.Node{ + ID: 10, + GivenName: "pidgeotto", + IPv4: ptrAddr("100.116.18.24"), + IPv6: ptrAddr("fd7a:115c:a1e0::ff37:1218"), + Tags: []string{"tag:exit-b"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + ApprovedRoutes: []netip.Prefix{ + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + } + + nodeRattata := &types.Node{ + ID: 11, + GivenName: "rattata", + IPv4: ptrAddr("100.107.162.14"), + IPv6: ptrAddr("fd7a:115c:a1e0::a237:a20e"), + Tags: []string{"tag:group-a"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeRaticate := &types.Node{ + ID: 12, + GivenName: "raticate", + IPv4: ptrAddr("100.77.135.18"), + IPv6: ptrAddr("fd7a:115c:a1e0::4b37:8712"), + Tags: []string{"tag:group-b"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + + nodeSpearow := &types.Node{ + ID: 13, + GivenName: "spearow", + IPv4: ptrAddr("100.109.43.124"), + IPv6: ptrAddr("fd7a:115c:a1e0::a537:2b7c"), + Tags: []string{"tag:router-a"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.44.0.0/16")}, + }, + ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.44.0.0/16")}, + } + + nodeFearow := &types.Node{ + ID: 14, + GivenName: "fearow", + IPv4: ptrAddr("100.65.172.123"), + IPv6: ptrAddr("fd7a:115c:a1e0::5a37:ac7c"), + Tags: []string{"tag:router-b"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.55.0.0/16")}, + }, + ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.55.0.0/16")}, + } + + nodeBlastoise := &types.Node{ + ID: 15, + GivenName: "blastoise", + IPv4: ptrAddr("100.105.127.107"), + IPv6: ptrAddr("fd7a:115c:a1e0::9537:7f6b"), + Tags: []string{"tag:exit", "tag:router"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{ + netip.MustParsePrefix("10.33.0.0/16"), + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + }, + ApprovedRoutes: []netip.Prefix{ + netip.MustParsePrefix("10.33.0.0/16"), + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("::/0"), + }, + } + + return types.Nodes{ + nodeBulbasaur, + nodeIvysaur, + nodeVenusaur, + nodeBeedrill, + nodeKakuna, + nodeWeedle, + nodeSquirtle, + nodeCharmander, + nodePidgey, + nodePidgeotto, + nodeRattata, + nodeRaticate, + nodeSpearow, + nodeFearow, + nodeBlastoise, + } +} + // findGrantsNode finds a node by its GivenName in the grants test environment. func findGrantsNode(nodes types.Nodes, name string) *types.Node { for _, n := range nodes { @@ -284,11 +496,18 @@ func testGrantSuccess( "golden node %s not found in test setup", nodeName) // Compile headscale filter rules for this node - gotRules := pol.compileFilterRulesForNode( + gotRules, err := pol.compileFilterRulesForNode( users, node.View(), nodes.ViewSlice(), ) + require.NoError( + t, + err, + "%s/%s: failed to compile filter rules", + tf.TestID, + nodeName, + ) gotRules = policyutil.ReduceFilterRules(node.View(), gotRules) diff --git a/hscontrol/policy/v2/tailscale_routes_data_compat_test.go b/hscontrol/policy/v2/tailscale_routes_data_compat_test.go index 978977940..660d90bcc 100644 --- a/hscontrol/policy/v2/tailscale_routes_data_compat_test.go +++ b/hscontrol/policy/v2/tailscale_routes_data_compat_test.go @@ -236,11 +236,18 @@ func TestRoutesCompat(t *testing.T) { require.NotNilf(t, node, "golden node %s not found in topology", nodeName) - compiledRules := pol.compileFilterRulesForNode( + compiledRules, err := pol.compileFilterRulesForNode( users, node.View(), nodes.ViewSlice(), ) + require.NoError( + t, + err, + "%s/%s: failed to compile filter rules", + tf.TestID, + nodeName, + ) gotRules := policyutil.ReduceFilterRules( node.View(), diff --git a/hscontrol/policy/v2/test.go b/hscontrol/policy/v2/test.go index da79f7c72..8e0f906a5 100644 --- a/hscontrol/policy/v2/test.go +++ b/hscontrol/policy/v2/test.go @@ -27,7 +27,7 @@ import ( // errPolicyTestsFailed and errSSHPolicyTestsFailed share the // "test(s) failed" prefix but stay distinct so callers can use -// [errors.Is] to tell ACL-test and SSH-test failures apart. +// errors.Is to tell ACL-test and SSH-test failures apart. var ( errPolicyTestsFailed = errors.New("test(s) failed") errSSHPolicyTestsFailed = errors.New("test(s) failed") @@ -55,7 +55,7 @@ type PolicyTest struct { // SSHPolicyTest is one entry in the policy's `sshTests` block. The // accept/deny/check arrays carry usernames, not destinations — every -// listed user is asserted against every entry in [SSHPolicyTest.Dst]. +// listed user is asserted against every entry in Dst. type SSHPolicyTest struct { // Src is a single source alias (user, group, tag, host, or IP). Src Alias `json:"src"` @@ -78,7 +78,7 @@ type SSHPolicyTest struct { } // SSHTestDestinations is the typed list of destination aliases an -// sshTests entry targets. [validateSSHTestDestination] enforces the +// sshTests entry targets. validateSSHTestDestination enforces the // SSH-specific shape rules (no :port, no CIDR, no autogroup:internet, // known tag). type SSHTestDestinations []Alias @@ -100,7 +100,7 @@ func (d *SSHTestDestinations) UnmarshalJSON(b []byte) error { } // UnmarshalJSON parses each typed field. An empty src lands as a nil -// [Alias] so validation surfaces [ErrSSHTestEmptySrc] rather than a parser +// Alias so validation surfaces ErrSSHTestEmptySrc rather than a parser // failure. func (t *SSHPolicyTest) UnmarshalJSON(b []byte) error { var raw struct { @@ -134,7 +134,7 @@ func (t *SSHPolicyTest) UnmarshalJSON(b []byte) error { return nil } -// PolicyTestResult is the outcome of a single [PolicyTest]. +// PolicyTestResult is the outcome of a single PolicyTest. type PolicyTestResult struct { Src string `json:"src"` Proto Protocol `json:"proto,omitempty"` @@ -196,17 +196,6 @@ func (r PolicyTestResults) Errors() string { return strings.Join(lines, "\n") } -// wrapTestResult returns nil when every test passed, otherwise wraps the -// rendered failure breakdown in sentinel. errs is passed uncalled so it is -// only evaluated on the failure path. -func wrapTestResult(sentinel error, allPassed bool, errs func() string) error { - if allPassed { - return nil - } - - return fmt.Errorf("%w:\n%s", sentinel, errs()) -} - // RunTests evaluates the policy's own `tests` block against the live compiled // filter and returns a wrapped error when any test fails. Callers that need // the per-test breakdown can call runPolicyTests directly. @@ -215,16 +204,19 @@ func (pm *PolicyManager) RunTests() error { return nil } - pm.mu.RLock() - defer pm.mu.RUnlock() + pm.mu.Lock() + defer pm.mu.Unlock() results := runPolicyTests(pm.pol, pm.filter, pm.users, pm.nodes) + if results.AllPassed { + return nil + } - return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results.Errors) + return fmt.Errorf("%w:\n%s", errPolicyTestsFailed, results.Errors()) } // evaluateTests runs the `tests` block against a fresh compilation of pol. -// It is the user-write sandbox: the live [PolicyManager] state is left +// It is the user-write sandbox: the live PolicyManager state is left // untouched, so a failing test rejects the write without side effects. func evaluateTests(pol *Policy, users []types.User, nodes views.Slice[types.NodeView]) error { if pol == nil || len(pol.Tests) == 0 { @@ -241,8 +233,11 @@ func evaluateTests(pol *Policy, users []types.User, nodes views.Slice[types.Node } results := runPolicyTests(pol, filter, users, nodes) + if results.AllPassed { + return nil + } - return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results.Errors) + return fmt.Errorf("%w:\n%s", errPolicyTestsFailed, results.Errors()) } // runPolicyTests is the pure evaluation function: given a policy, the @@ -267,7 +262,7 @@ func runPolicyTests(pol *Policy, filter []tailcfg.FilterRule, users []types.User return results } -// runPolicyTest evaluates one [PolicyTest]. +// runPolicyTest evaluates one PolicyTest. func runPolicyTest(test PolicyTest, pol *Policy, filter []tailcfg.FilterRule, users []types.User, nodes views.Slice[types.NodeView]) PolicyTestResult { res := PolicyTestResult{ Src: test.Src, @@ -290,34 +285,45 @@ func runPolicyTest(test PolicyTest, pol *Policy, filter []tailcfg.FilterRule, us return res } - check := func(dsts []string, wantAllowed bool, ok, fail *[]string) { - for _, dst := range dsts { - allowed, err := evalReachability(srcPrefixes, dst, test.Proto, pol, filter, users, nodes) - if err != nil { - res.Passed = false - res.Errors = append(res.Errors, fmt.Sprintf("error testing %q: %v", dst, err)) + for _, dst := range test.Accept { + allowed, err := evalReachability(srcPrefixes, dst, test.Proto, pol, filter, users, nodes) + if err != nil { + res.Passed = false + res.Errors = append(res.Errors, fmt.Sprintf("error testing %q: %v", dst, err)) - continue - } + continue + } - if allowed == wantAllowed { - *ok = append(*ok, dst) - } else { - res.Passed = false - - *fail = append(*fail, dst) - } + if allowed { + res.AcceptOK = append(res.AcceptOK, dst) + } else { + res.Passed = false + res.AcceptFail = append(res.AcceptFail, dst) } } - check(test.Accept, true, &res.AcceptOK, &res.AcceptFail) - check(test.Deny, false, &res.DenyOK, &res.DenyFail) + for _, dst := range test.Deny { + allowed, err := evalReachability(srcPrefixes, dst, test.Proto, pol, filter, users, nodes) + if err != nil { + res.Passed = false + res.Errors = append(res.Errors, fmt.Sprintf("error testing %q: %v", dst, err)) + + continue + } + + if !allowed { + res.DenyOK = append(res.DenyOK, dst) + } else { + res.Passed = false + res.DenyFail = append(res.DenyFail, dst) + } + } return res } -// resolveTestSource resolves the Src alias of a [PolicyTest] into a slice of -// [netip.Prefix]. [parseAlias] + [Alias.Resolve] cover every alias type the rest +// resolveTestSource resolves the Src alias of a PolicyTest into a slice of +// netip.Prefix. parseAlias + Alias.Resolve cover every alias type the rest // of the policy engine supports, so tests inherit alias semantics for free. func resolveTestSource(src string, pol *Policy, users []types.User, nodes views.Slice[types.NodeView]) ([]netip.Prefix, error) { alias, err := parseAlias(src) @@ -371,13 +377,13 @@ func evalReachability(srcPrefixes []netip.Prefix, dst string, proto Protocol, po return true, nil } -// parseDestinationAlias is a thin wrapper over [AliasWithPorts.UnmarshalJSON] +// parseDestinationAlias is a thin wrapper over AliasWithPorts.UnmarshalJSON // so callers can hand it a bare `"host:port"` string without re-implementing // the parse logic. func parseDestinationAlias(dst string) (*AliasWithPorts, error) { var awp AliasWithPorts - // [AliasWithPorts.UnmarshalJSON] expects a quoted JSON string, so wrap. + // AliasWithPorts.UnmarshalJSON expects a quoted JSON string, so wrap. err := awp.UnmarshalJSON([]byte(`"` + dst + `"`)) if err != nil { return nil, err @@ -419,9 +425,9 @@ func srcReachesDst(src netip.Prefix, dstPrefixes []netip.Prefix, ports []tailcfg } // ruleMatchesSource reports whether the rule's source list contains src. -// [tailcfg.FilterRule.SrcIPs] may be CIDR, single addresses, IP ranges (`a-b`), -// or `*`; we use [util.ParseIPSet] to cover all of those uniformly. Unparseable -// entries are skipped (the rule compiler emits well-formed strings, so this is +// SrcIPs may be CIDR, single addresses, IP ranges (`a-b`), or `*`; we use +// util.ParseIPSet to cover all of those uniformly. Unparseable entries +// are skipped (the rule compiler emits well-formed strings, so this is // defence-in-depth, not error handling). func ruleMatchesSource(rule tailcfg.FilterRule, src netip.Prefix) bool { for _, raw := range rule.SrcIPs { @@ -439,10 +445,9 @@ func ruleMatchesSource(rule tailcfg.FilterRule, src netip.Prefix) bool { } // ruleMatchesProto reports whether the rule permits any of requestedProtos. -// An unset [tailcfg.FilterRule.IPProto] means "any protocol" and matches -// everything. requestedProtos is the per-test protocol set: a single proto -// for an explicit [PolicyTest.Proto], or the default set when -// [PolicyTest.Proto] is empty. +// An unset rule.IPProto means "any protocol" and matches everything. +// requestedProtos is the per-test protocol set: a single proto for an +// explicit test.Proto, or the default set when test.Proto is empty. func ruleMatchesProto(rule tailcfg.FilterRule, requestedProtos []int) bool { if len(rule.IPProto) == 0 { return true @@ -474,7 +479,7 @@ func ruleAllowsAnyDest(rule tailcfg.FilterRule, dstPrefixes []netip.Prefix, port return false } -// destEntryMatchesPrefixes reports whether the rule's [tailcfg.NetPortRange.IP] +// destEntryMatchesPrefixes reports whether the rule's NetPortRange.IP // (CIDR, single IP, IP range, or "*") covers any prefix in dstPrefixes. func destEntryMatchesPrefixes(dp tailcfg.NetPortRange, dstPrefixes []netip.Prefix) bool { set, err := util.ParseIPSet(dp.IP, nil) diff --git a/hscontrol/policy/v2/testdata/grant_results/via-grant-v47.hujson b/hscontrol/policy/v2/testdata/grant_results/via-grant-v47.hujson deleted file mode 100644 index 4a46ee03b..000000000 --- a/hscontrol/policy/v2/testdata/grant_results/via-grant-v47.hujson +++ /dev/null @@ -1,14164 +0,0 @@ -// via-grant-v47 -// -// via grant v47 dst literal cidr broader than advertised subnet -// -// Nodes with filter rules: 2 of 15 -// Captured at: 2026-05-18T09:07:30Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "via-grant-v47", - "description": "via grant v47 dst literal cidr broader than advertised subnet", - "category": "grant", - "captured_at": "2026-05-18T09:07:30.233896341Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n \n \n \n \n \n{\n\t\"category\": \"grant\",\n\t\"description\": \"via grant v47 dst literal cidr broader than advertised subnet\",\n\t\"id\": \"via-grant-v47\",\n\t\"options\": {\"capture_whois\": true},\n\t\"policy\": {\"autoApprovers\": {\"routes\": {\n\t\t\"10.33.0.0/16\": [\"tag:router\"],\n\t\t\"10.44.0.0/16\": [\"tag:spearow\"],\n\t\t\"10.55.0.0/16\": [\"tag:fearow\"]\n\t}}, \"grants\": [{\n\t\t\"dst\": [\"10.0.0.0/8\"],\n\t\t\"ip\": [\"*\"],\n\t\t\"src\": [\"tag:group-a\"],\n\t\t\"via\": [\"tag:router\"]\n\t}], \"groups\": {\n\t\t\"group:admins\": [\"odin@example.com\"],\n\t\t\"group:developers\": [\"thor@example.org\", \"odin@example.com\"],\n\t\t\"group:empty\": [],\n\t\t\"group:monitors\": [\"freya@example.com\"]\n\t}, \"hosts\": {\n\t\t\"internal\": \"10.0.0.0/8\",\n\t\t\"prodbox\": \"100.103.8.15\",\n\t\t\"webserver\": \"100.108.74.26\"\n\t}, \"tagOwners\": {\n\t\t\"tag:client\": [\"odin@example.com\"],\n\t\t\"tag:exit\": [\"odin@example.com\"],\n\t\t\"tag:pidgey\": [\"odin@example.com\"],\n\t\t\"tag:pidgeotto\": [\"odin@example.com\"],\n\t\t\"tag:group-a\": [\"odin@example.com\"],\n\t\t\"tag:group-b\": [\"odin@example.com\"],\n\t\t\"tag:ha\": [\"odin@example.com\"],\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:router\": [\"odin@example.com\"],\n\t\t\"tag:spearow\": [\"odin@example.com\"],\n\t\t\"tag:fearow\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/grant/via-grant-v47.hujson", - "full_policy": {"autoApprovers": {"routes": { - "10.33.0.0/16": ["tag:router"], - "10.44.0.0/16": ["tag:spearow"], - "10.55.0.0/16": ["tag:fearow"] - }}, "grants": [{ - "dst": ["10.0.0.0/8"], - "ip": ["*"], - "src": ["tag:group-a"], - "via": ["tag:router"] - }], "groups": { - "group:admins": ["odin@example.com"], - "group:developers": ["thor@example.org", "odin@example.com"], - "group:empty": [], - "group:monitors": ["freya@example.com"] - }, "hosts": { - "internal": "10.0.0.0/8", - "prodbox": "100.103.8.15", - "webserver": "100.108.74.26" - }, "tagOwners": { - "tag:client": ["odin@example.com"], - "tag:exit": ["odin@example.com"], - "tag:pidgey": ["odin@example.com"], - "tag:pidgeotto": ["odin@example.com"], - "tag:group-a": ["odin@example.com"], - "tag:group-b": ["odin@example.com"], - "tag:ha": ["odin@example.com"], - "tag:prod": ["odin@example.com"], - "tag:router": ["odin@example.com"], - "tag:spearow": ["odin@example.com"], - "tag:fearow": ["odin@example.com"], - "tag:server": ["odin@example.com"] - }} - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": ["10.33.0.0/16"] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": ["10.55.0.0/16"] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": ["10.44.0.0/16"] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": ["10.33.0.0/16"] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5981618237305639, - "StableID": "nk5QY9y5io11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 5981618237305639, - "Key": "nodekey:8d00dadb71723afae9a5a66f91f0969d976e421e425f9a59bf724af8ad0be044", - "DiscoKey": "discokey:58a7b5d39f051fe68397efb26faf8750e5e02e9f63bd44898ab0d2072a95b606", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:40707", - "10.65.0.27:40707", - "172.17.0.1:40707", - "172.18.0.1:40707", - "172.19.0.1:40707", - "172.20.0.1:40707", - "172.21.0.1:40707", - "172.22.0.1:40707", - "172.23.0.1:40707", - "172.24.0.1:40707", - "172.25.0.1:40707", - "172.27.0.1:40707", - "172.28.0.1:40707", - "172.29.0.1:40707", - "172.30.0.1:40707" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:42.555612283Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8d00dadb71723afae9a5a66f91f0969d976e421e425f9a59bf724af8ad0be044", - "MachineKey": "mkey:c0f8e65db1b7d40ade64d7f79b1b6e8357d0b039822dfb0c2064aefe4d8d556f", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5981618237305639": { - "ID": 5981618237305639, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }}, "blastoise": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2473353104810224, - "StableID": "n9wnGwnBKL11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 2473353104810224, - "Key": "nodekey:53576a2afaa4b69cb90015c53612c7dfd379f0228b834c69c259c4e6fca4b126", - "DiscoKey": "discokey:ff7cbde611949dd26d88c1b8fb4f44eb7c0d088d56f6a27b55d877b998c19154", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:34777", - "10.65.0.27:34777", - "172.17.0.1:34777", - "172.18.0.1:34777", - "172.19.0.1:34777", - "172.20.0.1:34777", - "172.21.0.1:34777", - "172.22.0.1:34777", - "172.23.0.1:34777", - "172.24.0.1:34777", - "172.25.0.1:34777", - "172.27.0.1:34777", - "172.28.0.1:34777", - "172.29.0.1:34777", - "172.30.0.1:34777" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:39.354833108Z", - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:53576a2afaa4b69cb90015c53612c7dfd379f0228b834c69c259c4e6fca4b126", - "MachineKey": "mkey:fb2a0b9fb8f781ee7920910a5139ef6a349da317cd3599045dc362211893c32b", - "Peers": [{ - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2473353104810224": { - "ID": 2473353104810224, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "bulbasaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 3735169030154237, - "StableID": "n239iHZfAW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:a774215fe8e225a2f8e3512976b66d28c99f4ec53ce5e8a9a0151bc5e6823f19", - "KeyExpiry": "2026-11-14T09:07:44Z", - "DiscoKey": "discokey:5ed049b7a2ab5d6db0cb238f24853358f630e8732941ab2080352cde120a8424", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:52052", - "10.65.0.27:52052", - "172.17.0.1:52052", - "172.18.0.1:52052", - "172.19.0.1:52052", - "172.20.0.1:52052", - "172.21.0.1:52052", - "172.22.0.1:52052", - "172.23.0.1:52052", - "172.24.0.1:52052", - "172.25.0.1:52052", - "172.27.0.1:52052", - "172.28.0.1:52052", - "172.29.0.1:52052", - "172.30.0.1:52052" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:44.156697605Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a774215fe8e225a2f8e3512976b66d28c99f4ec53ce5e8a9a0151bc5e6823f19", - "MachineKey": "mkey:1a4b414c9ccd8577fb02ab172093ad7e402a207afd92586edea123a590100930", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }} - }}, "charmander": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2713035784821404, - "StableID": "nu57xGrjBN11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 2713035784821404, - "Key": "nodekey:5cb9f7acbae73edf559e1377876b21dc4623f284b4910afa113fee4d5b3caa22", - "DiscoKey": "discokey:547d15f2e245fa1cfd074dde54335c3bcea322326dd09b2396b94a2cee1e6616", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:39812", - "10.65.0.27:39812", - "172.17.0.1:39812", - "172.18.0.1:39812", - "172.19.0.1:39812", - "172.20.0.1:39812", - "172.21.0.1:39812", - "172.22.0.1:39812", - "172.23.0.1:39812", - "172.24.0.1:39812", - "172.25.0.1:39812", - "172.27.0.1:39812", - "172.28.0.1:39812", - "172.29.0.1:39812", - "172.30.0.1:39812" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:37.761033386Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:5cb9f7acbae73edf559e1377876b21dc4623f284b4910afa113fee4d5b3caa22", - "MachineKey": "mkey:57893d73ba8b22cc2ea924982dfcf12560269df0307d293cbbd432dddcda771f", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"2713035784821404": { - "ID": 2713035784821404, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - }}, "fearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1036298269181620, - "StableID": "n9BqQJoL6911CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1036298269181620, - "Key": "nodekey:8c80f779c4b3f88a3cea46596bc465f7c32b0e495ce49f8163f3dab22eb32027", - "DiscoKey": "discokey:087e934fddc3d13002704b01725a7befd7c406315e4b4061938dc791626e8c31", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128", "10.55.0.0/16"], - "Endpoints": [ - "77.164.248.136:58140", - "10.65.0.27:58140", - "172.17.0.1:58140", - "172.18.0.1:58140", - "172.19.0.1:58140", - "172.20.0.1:58140", - "172.21.0.1:58140", - "172.22.0.1:58140", - "172.23.0.1:58140", - "172.24.0.1:58140", - "172.25.0.1:58140", - "172.27.0.1:58140", - "172.28.0.1:58140", - "172.29.0.1:58140", - "172.30.0.1:58140" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:40.418352282Z", - "Tags": ["tag:fearow"], - "PrimaryRoutes": ["10.55.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8c80f779c4b3f88a3cea46596bc465f7c32b0e495ce49f8163f3dab22eb32027", - "MachineKey": "mkey:b61388ca8350efc3e792f6ccf81a353d0e56cb12b010490006bea4ff8850ea53", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1036298269181620": { - "ID": 1036298269181620, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - }}, "ivysaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6052475718574476, - "StableID": "nRjDXtGBGp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:bf95ec3ca0ef9fcf414d6c2dbcde1ad167462596f0302534a4fb0599de2d7608", - "KeyExpiry": "2026-11-14T09:07:43Z", - "DiscoKey": "discokey:d2847f8e1119447612990448a50b04d407f3ef9c60b84d2087dc528f4c7d102c", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:47977", - "10.65.0.27:47977", - "172.17.0.1:47977", - "172.18.0.1:47977", - "172.19.0.1:47977", - "172.20.0.1:47977", - "172.21.0.1:47977", - "172.22.0.1:47977", - "172.23.0.1:47977", - "172.24.0.1:47977", - "172.25.0.1:47977", - "172.27.0.1:47977", - "172.28.0.1:47977", - "172.29.0.1:47977", - "172.30.0.1:47977" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:43.094398266Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:bf95ec3ca0ef9fcf414d6c2dbcde1ad167462596f0302534a4fb0599de2d7608", - "MachineKey": "mkey:11c887185e0c4768aec007945b24a597760b1179c62ff13f0abd215d5e031a10", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - }}, "kakuna": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 794053188242122, - "StableID": "n5ky8zRdC711CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 794053188242122, - "Key": "nodekey:0e51741477cf6d0151375bef8bf17b20be798ecfc80211cd2b5242a9357e320e", - "DiscoKey": "discokey:2bca03dbb97dbdb2cc5edafc535dbe06f25ec21f76c7827ff827c1d30b593c19", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:59649", - "10.65.0.27:59649", - "172.17.0.1:59649", - "172.18.0.1:59649", - "172.19.0.1:59649", - "172.20.0.1:59649", - "172.21.0.1:59649", - "172.22.0.1:59649", - "172.23.0.1:59649", - "172.24.0.1:59649", - "172.25.0.1:59649", - "172.27.0.1:59649", - "172.28.0.1:59649", - "172.29.0.1:59649", - "172.30.0.1:59649" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:42.022585026Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:0e51741477cf6d0151375bef8bf17b20be798ecfc80211cd2b5242a9357e320e", - "MachineKey": "mkey:5e46df86af2cd5bfacbc9f7d0526d488a8be09677e7b852e4fe037211bc8af17", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"794053188242122": { - "ID": 794053188242122, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - }}, "pidgeotto": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4812977064092943, - "StableID": "nG1XtGkoae11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 4812977064092943, - "Key": "nodekey:9d1fd36fc77794676d764f15ba49b55085ba4d30da84cfcde57ab42304ec6a6b", - "DiscoKey": "discokey:811005c8eb7cd134ad3a54ec8646d14a18c9c6f437943fd5518f232685e31b07", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:57034", - "10.65.0.27:57034", - "172.17.0.1:57034", - "172.18.0.1:57034", - "172.19.0.1:57034", - "172.20.0.1:57034", - "172.21.0.1:57034", - "172.22.0.1:57034", - "172.23.0.1:57034", - "172.24.0.1:57034", - "172.25.0.1:57034", - "172.27.0.1:57034", - "172.28.0.1:57034", - "172.29.0.1:57034", - "172.30.0.1:57034" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:37.243602289Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:9d1fd36fc77794676d764f15ba49b55085ba4d30da84cfcde57ab42304ec6a6b", - "MachineKey": "mkey:cc92e5235f0a5f4f21f80e5501305887c00be15fc602099fe62772fac452d518", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4812977064092943": { - "ID": 4812977064092943, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - }}, "pidgey": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8216591946215860, - "StableID": "n5PctNwJA721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 8216591946215860, - "Key": "nodekey:f54e53dd1c4183ffc21e79366245cd9ca3388580ab0693eddd6f22e666f6ce04", - "DiscoKey": "discokey:5d7953f92951857532d9b605a02b7c630349de3174d0c2b6cdfebb970d766260", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:42913", - "10.65.0.27:42913", - "172.17.0.1:42913", - "172.18.0.1:42913", - "172.19.0.1:42913", - "172.20.0.1:42913", - "172.21.0.1:42913", - "172.22.0.1:42913", - "172.23.0.1:42913", - "172.24.0.1:42913", - "172.25.0.1:42913", - "172.27.0.1:42913", - "172.28.0.1:42913", - "172.29.0.1:42913", - "172.30.0.1:42913" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:36.71265978Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f54e53dd1c4183ffc21e79366245cd9ca3388580ab0693eddd6f22e666f6ce04", - "MachineKey": "mkey:4c30a202339842a91c2e7ea26434618557cb1d565ca9fab62b2b4cf4b9c0f456", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8216591946215860": { - "ID": 8216591946215860, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - }}, "raticate": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8892810857496319, - "StableID": "ntyeUa4aSC21CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 8892810857496319, - "Key": "nodekey:1a7845aef659eda1c859138a1cc5bf0c67a2558c91b339fe6ac5800dcda6ae5a", - "DiscoKey": "discokey:59fae4ca38649d03d183574d5bf8f4c73875161d00416df26079d973a3387561", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:34561", - "10.65.0.27:34561", - "172.17.0.1:34561", - "172.18.0.1:34561", - "172.19.0.1:34561", - "172.20.0.1:34561", - "172.21.0.1:34561", - "172.22.0.1:34561", - "172.23.0.1:34561", - "172.24.0.1:34561", - "172.25.0.1:34561", - "172.27.0.1:34561", - "172.28.0.1:34561", - "172.29.0.1:34561", - "172.30.0.1:34561" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:38.818449728Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:1a7845aef659eda1c859138a1cc5bf0c67a2558c91b339fe6ac5800dcda6ae5a", - "MachineKey": "mkey:9a874ea341056cfa481dc395dd2bc7cd1a286ab6b6ab02b644a82fb348539a63", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8892810857496319": { - "ID": 8892810857496319, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - }}, "rattata": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 2235849634173111, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "MachineKey": "mkey:11b1a37b8ec4366a8bb0973dcbe374c151cdb60c99d70b7ea095c75f126e1838", - "Peers": [{ - "ID": 2473353104810224, - "StableID": "n9wnGwnBKL11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53576a2afaa4b69cb90015c53612c7dfd379f0228b834c69c259c4e6fca4b126", - "DiscoKey": "discokey:ff7cbde611949dd26d88c1b8fb4f44eb7c0d088d56f6a27b55d877b998c19154", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:34777", - "10.65.0.27:34777", - "172.17.0.1:34777", - "172.18.0.1:34777", - "172.19.0.1:34777", - "172.20.0.1:34777", - "172.21.0.1:34777", - "172.22.0.1:34777", - "172.23.0.1:34777", - "172.24.0.1:34777", - "172.25.0.1:34777", - "172.27.0.1:34777", - "172.28.0.1:34777", - "172.29.0.1:34777", - "172.30.0.1:34777" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:07:39.354833108Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 3041776490751602, - "StableID": "nyvyQ1KdkQ11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:50b6a0a0d1e146d32491c4755b27bdbf70c363b6e9e1d93f4352e6950a7c4654", - "DiscoKey": "discokey:3635aa1d34b3d62fd7d260145cb8e9a1ff0358707f6767f22bc6f4da95517169", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:34905", - "10.65.0.27:34905", - "172.17.0.1:34905", - "172.18.0.1:34905", - "172.19.0.1:34905", - "172.20.0.1:34905", - "172.21.0.1:34905", - "172.22.0.1:34905", - "172.23.0.1:34905", - "172.24.0.1:34905", - "172.25.0.1:34905", - "172.27.0.1:34905", - "172.28.0.1:34905", - "172.29.0.1:34905", - "172.30.0.1:34905" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:07:40.954380299Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2235849634173111": { - "ID": 2235849634173111, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }} - }, "whois": {"100.64.0.13": {"Node": { - "ID": 3041776490751602, - "StableID": "nyvyQ1KdkQ11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:50b6a0a0d1e146d32491c4755b27bdbf70c363b6e9e1d93f4352e6950a7c4654", - "DiscoKey": "discokey:3635aa1d34b3d62fd7d260145cb8e9a1ff0358707f6767f22bc6f4da95517169", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:34905", - "10.65.0.27:34905", - "172.17.0.1:34905", - "172.18.0.1:34905", - "172.19.0.1:34905", - "172.20.0.1:34905", - "172.21.0.1:34905", - "172.22.0.1:34905", - "172.23.0.1:34905", - "172.24.0.1:34905", - "172.25.0.1:34905", - "172.27.0.1:34905", - "172.28.0.1:34905", - "172.29.0.1:34905", - "172.30.0.1:34905" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:07:40.954380299Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "100.64.0.9": {"Node": { - "ID": 2473353104810224, - "StableID": "n9wnGwnBKL11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53576a2afaa4b69cb90015c53612c7dfd379f0228b834c69c259c4e6fca4b126", - "DiscoKey": "discokey:ff7cbde611949dd26d88c1b8fb4f44eb7c0d088d56f6a27b55d877b998c19154", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:34777", - "10.65.0.27:34777", - "172.17.0.1:34777", - "172.18.0.1:34777", - "172.19.0.1:34777", - "172.20.0.1:34777", - "172.21.0.1:34777", - "172.22.0.1:34777", - "172.23.0.1:34777", - "172.24.0.1:34777", - "172.25.0.1:34777", - "172.27.0.1:34777", - "172.28.0.1:34777", - "172.29.0.1:34777", - "172.30.0.1:34777" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:07:39.354833108Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::9": {"Node": { - "ID": 2473353104810224, - "StableID": "n9wnGwnBKL11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53576a2afaa4b69cb90015c53612c7dfd379f0228b834c69c259c4e6fca4b126", - "DiscoKey": "discokey:ff7cbde611949dd26d88c1b8fb4f44eb7c0d088d56f6a27b55d877b998c19154", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:34777", - "10.65.0.27:34777", - "172.17.0.1:34777", - "172.18.0.1:34777", - "172.19.0.1:34777", - "172.20.0.1:34777", - "172.21.0.1:34777", - "172.22.0.1:34777", - "172.23.0.1:34777", - "172.24.0.1:34777", - "172.25.0.1:34777", - "172.27.0.1:34777", - "172.28.0.1:34777", - "172.29.0.1:34777", - "172.30.0.1:34777" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:07:39.354833108Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::d": {"Node": { - "ID": 3041776490751602, - "StableID": "nyvyQ1KdkQ11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:50b6a0a0d1e146d32491c4755b27bdbf70c363b6e9e1d93f4352e6950a7c4654", - "DiscoKey": "discokey:3635aa1d34b3d62fd7d260145cb8e9a1ff0358707f6767f22bc6f4da95517169", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:34905", - "10.65.0.27:34905", - "172.17.0.1:34905", - "172.18.0.1:34905", - "172.19.0.1:34905", - "172.20.0.1:34905", - "172.21.0.1:34905", - "172.22.0.1:34905", - "172.23.0.1:34905", - "172.24.0.1:34905", - "172.25.0.1:34905", - "172.27.0.1:34905", - "172.28.0.1:34905", - "172.29.0.1:34905", - "172.30.0.1:34905" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:07:40.954380299Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "spearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2799795516713445, - "StableID": "nQv2N5t2sN11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 2799795516713445, - "Key": "nodekey:1add08b06d606dadeada63b16c9f03e40caba4b5f8578bc14c687e6a25b1ac7a", - "DiscoKey": "discokey:6610b340849c87be81ab4747629a14f6e473a71cc0ea8da4acfbbb2d485dff55", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128", "10.44.0.0/16"], - "Endpoints": [ - "77.164.248.136:39652", - "10.65.0.27:39652", - "172.17.0.1:39652", - "172.18.0.1:39652", - "172.19.0.1:39652", - "172.20.0.1:39652", - "172.21.0.1:39652", - "172.22.0.1:39652", - "172.23.0.1:39652", - "172.24.0.1:39652", - "172.25.0.1:39652", - "172.27.0.1:39652", - "172.28.0.1:39652", - "172.29.0.1:39652", - "172.30.0.1:39652" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:39.890624348Z", - "Tags": ["tag:spearow"], - "PrimaryRoutes": ["10.44.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:1add08b06d606dadeada63b16c9f03e40caba4b5f8578bc14c687e6a25b1ac7a", - "MachineKey": "mkey:b93eabce8cd9b6798a4dc31251d1e04b02102f4f5269e53c0ff2a33e3382aa18", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"2799795516713445": { - "ID": 2799795516713445, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - }}, "squirtle": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3041776490751602, - "StableID": "nyvyQ1KdkQ11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 3041776490751602, - "Key": "nodekey:50b6a0a0d1e146d32491c4755b27bdbf70c363b6e9e1d93f4352e6950a7c4654", - "DiscoKey": "discokey:3635aa1d34b3d62fd7d260145cb8e9a1ff0358707f6767f22bc6f4da95517169", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:34905", - "10.65.0.27:34905", - "172.17.0.1:34905", - "172.18.0.1:34905", - "172.19.0.1:34905", - "172.20.0.1:34905", - "172.21.0.1:34905", - "172.22.0.1:34905", - "172.23.0.1:34905", - "172.24.0.1:34905", - "172.25.0.1:34905", - "172.27.0.1:34905", - "172.28.0.1:34905", - "172.29.0.1:34905", - "172.30.0.1:34905" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:07:40.954380299Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:50b6a0a0d1e146d32491c4755b27bdbf70c363b6e9e1d93f4352e6950a7c4654", - "MachineKey": "mkey:f39e2e6f5ed734cb72eb5f65a803a4e85b2aadfa1c73df8fbe89653b4efca311", - "Peers": [{ - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3041776490751602": { - "ID": 3041776490751602, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 2235849634173111, - "StableID": "nLB9skycTJ11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6db2017d3818f302a7d230746785041dbb0314a538f5d655931ebca7add23541", - "DiscoKey": "discokey:483f647524f0e221cc993febe44f860f82f23540a3efa803468d7bae22cb003d", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:43793", - "10.65.0.27:43793", - "172.17.0.1:43793", - "172.18.0.1:43793", - "172.19.0.1:43793", - "172.20.0.1:43793", - "172.21.0.1:43793", - "172.22.0.1:43793", - "172.23.0.1:43793", - "172.24.0.1:43793", - "172.25.0.1:43793", - "172.27.0.1:43793", - "172.28.0.1:43793", - "172.29.0.1:43793", - "172.30.0.1:43793" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:07:38.293173157Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "venusaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2373815828419161, - "StableID": "nCWQ1m77YK11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:06e4dabcb61a95f21c7addb5a986754fbe29b7b78793757eb8ad16823c17936f", - "KeyExpiry": "2026-11-14T09:07:43Z", - "DiscoKey": "discokey:01f918807dbcd783cbec6fc8a68db8349ba155d45b222bc28528d647831a7b64", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:56337", - "10.65.0.27:56337", - "172.17.0.1:56337", - "172.18.0.1:56337", - "172.19.0.1:56337", - "172.20.0.1:56337", - "172.21.0.1:56337", - "172.22.0.1:56337", - "172.23.0.1:56337", - "172.24.0.1:56337", - "172.25.0.1:56337", - "172.27.0.1:56337", - "172.28.0.1:56337", - "172.29.0.1:56337", - "172.30.0.1:56337" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:43.621164687Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:06e4dabcb61a95f21c7addb5a986754fbe29b7b78793757eb8ad16823c17936f", - "MachineKey": "mkey:907eebd2a1d7c7f88e175e6db66c386073b22db0f4fb47d9089bb48c7fb14d7a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }} - }}, "weedle": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5012072571545429, - "StableID": "nAKT7Zey8g11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 5012072571545429, - "Key": "nodekey:240035543acf1f0c2d2f9d4c0f6eeed6500f8805c7d43e74e06b6ae1ebb19578", - "DiscoKey": "discokey:da4a9c01d0028620fbc9b91a315542a2e2b2931aabe6bdf692de73234e255945", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:35714", - "10.65.0.27:35714", - "172.17.0.1:35714", - "172.18.0.1:35714", - "172.19.0.1:35714", - "172.20.0.1:35714", - "172.21.0.1:35714", - "172.22.0.1:35714", - "172.23.0.1:35714", - "172.24.0.1:35714", - "172.25.0.1:35714", - "172.27.0.1:35714", - "172.28.0.1:35714", - "172.29.0.1:35714", - "172.30.0.1:35714" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:07:41.490756459Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:240035543acf1f0c2d2f9d4c0f6eeed6500f8805c7d43e74e06b6ae1ebb19578", - "MachineKey": "mkey:28507eb157e4d6f03845a0ab0be1d48b083f3bfef571ccc9cd3d294e0cc2c656", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5012072571545429": { - "ID": 5012072571545429, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - }}} -} diff --git a/hscontrol/policy/v2/testdata/grant_results/via-grant-v48.hujson b/hscontrol/policy/v2/testdata/grant_results/via-grant-v48.hujson deleted file mode 100644 index 807e346af..000000000 --- a/hscontrol/policy/v2/testdata/grant_results/via-grant-v48.hujson +++ /dev/null @@ -1,14164 +0,0 @@ -// via-grant-v48 -// -// via grant v48 dst cidr narrower than advertised subnet -// -// Nodes with filter rules: 2 of 15 -// Captured at: 2026-05-18T09:08:45Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "via-grant-v48", - "description": "via grant v48 dst cidr narrower than advertised subnet", - "category": "grant", - "captured_at": "2026-05-18T09:08:45.834537146Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n \n \n \n \n \n{\n\t\"category\": \"grant\",\n\t\"description\": \"via grant v48 dst cidr narrower than advertised subnet\",\n\t\"id\": \"via-grant-v48\",\n\t\"options\": {\"capture_whois\": true},\n\t\"policy\": {\"autoApprovers\": {\"routes\": {\n\t\t\"10.33.0.0/16\": [\"tag:router\"],\n\t\t\"10.44.0.0/16\": [\"tag:spearow\"],\n\t\t\"10.55.0.0/16\": [\"tag:fearow\"]\n\t}}, \"grants\": [{\n\t\t\"dst\": [\"10.33.5.0/24\"],\n\t\t\"ip\": [\"*\"],\n\t\t\"src\": [\"tag:group-a\"],\n\t\t\"via\": [\"tag:router\"]\n\t}], \"groups\": {\n\t\t\"group:admins\": [\"odin@example.com\"],\n\t\t\"group:developers\": [\"thor@example.org\", \"odin@example.com\"],\n\t\t\"group:empty\": [],\n\t\t\"group:monitors\": [\"freya@example.com\"]\n\t}, \"hosts\": {\n\t\t\"internal\": \"10.0.0.0/8\",\n\t\t\"prodbox\": \"100.103.8.15\",\n\t\t\"webserver\": \"100.108.74.26\"\n\t}, \"tagOwners\": {\n\t\t\"tag:client\": [\"odin@example.com\"],\n\t\t\"tag:exit\": [\"odin@example.com\"],\n\t\t\"tag:pidgey\": [\"odin@example.com\"],\n\t\t\"tag:pidgeotto\": [\"odin@example.com\"],\n\t\t\"tag:group-a\": [\"odin@example.com\"],\n\t\t\"tag:group-b\": [\"odin@example.com\"],\n\t\t\"tag:ha\": [\"odin@example.com\"],\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:router\": [\"odin@example.com\"],\n\t\t\"tag:spearow\": [\"odin@example.com\"],\n\t\t\"tag:fearow\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/grant/via-grant-v48.hujson", - "full_policy": {"autoApprovers": {"routes": { - "10.33.0.0/16": ["tag:router"], - "10.44.0.0/16": ["tag:spearow"], - "10.55.0.0/16": ["tag:fearow"] - }}, "grants": [{ - "dst": ["10.33.5.0/24"], - "ip": ["*"], - "src": ["tag:group-a"], - "via": ["tag:router"] - }], "groups": { - "group:admins": ["odin@example.com"], - "group:developers": ["thor@example.org", "odin@example.com"], - "group:empty": [], - "group:monitors": ["freya@example.com"] - }, "hosts": { - "internal": "10.0.0.0/8", - "prodbox": "100.103.8.15", - "webserver": "100.108.74.26" - }, "tagOwners": { - "tag:client": ["odin@example.com"], - "tag:exit": ["odin@example.com"], - "tag:pidgey": ["odin@example.com"], - "tag:pidgeotto": ["odin@example.com"], - "tag:group-a": ["odin@example.com"], - "tag:group-b": ["odin@example.com"], - "tag:ha": ["odin@example.com"], - "tag:prod": ["odin@example.com"], - "tag:router": ["odin@example.com"], - "tag:spearow": ["odin@example.com"], - "tag:fearow": ["odin@example.com"], - "tag:server": ["odin@example.com"] - }} - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": ["10.33.0.0/16"] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": ["10.55.0.0/16"] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": ["10.44.0.0/16"] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": ["10.33.0.0/16"] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4939789463153043, - "StableID": "nGwyLntEaf11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 4939789463153043, - "Key": "nodekey:96b07b5c72419047faaabdb30aa4ca151710d6fe857e1f8237754a1916ff4a57", - "DiscoKey": "discokey:e6e3feefec412217a27ea78b94a14cab01b5d9c2a29598c75f05eea069d95831", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:54990", - "10.65.0.27:54990", - "172.17.0.1:54990", - "172.18.0.1:54990", - "172.19.0.1:54990", - "172.20.0.1:54990", - "172.21.0.1:54990", - "172.22.0.1:54990", - "172.23.0.1:54990", - "172.24.0.1:54990", - "172.25.0.1:54990", - "172.27.0.1:54990", - "172.28.0.1:54990", - "172.29.0.1:54990", - "172.30.0.1:54990" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:58.527680896Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:96b07b5c72419047faaabdb30aa4ca151710d6fe857e1f8237754a1916ff4a57", - "MachineKey": "mkey:1310795e2e9a2d1e0af99f732ec631ca81bb9b6cba72c536f3b880f0416a4b1a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4939789463153043": { - "ID": 4939789463153043, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }}, "blastoise": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8665747210852025, - "StableID": "nWHpZ7VjfA21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 8665747210852025, - "Key": "nodekey:e1997bcdec8da2b3320583a153b55ef42e4e433fc0defdab0f2db8cb208d680e", - "DiscoKey": "discokey:9dd4f0e3b72e928e8322334201d4688a2ba265b7bb17cb6715e83cd5d1665220", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59708", - "10.65.0.27:59708", - "172.17.0.1:59708", - "172.18.0.1:59708", - "172.19.0.1:59708", - "172.20.0.1:59708", - "172.21.0.1:59708", - "172.22.0.1:59708", - "172.23.0.1:59708", - "172.24.0.1:59708", - "172.25.0.1:59708", - "172.27.0.1:59708", - "172.28.0.1:59708", - "172.29.0.1:59708", - "172.30.0.1:59708" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:55.326839794Z", - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e1997bcdec8da2b3320583a153b55ef42e4e433fc0defdab0f2db8cb208d680e", - "MachineKey": "mkey:af25fdb9638960447bf18984caabd69885168812fc7a95c719e5df40b2c5a079", - "Peers": [{ - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "8665747210852025": { - "ID": 8665747210852025, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "bulbasaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6568888912785811, - "StableID": "npFw7Ea4Jt11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:4ddd367622cc084368575c6d7d1b656e261dae5e9335999a22418e340202ba3c", - "KeyExpiry": "2026-11-14T09:09:00Z", - "DiscoKey": "discokey:fc90c3aec724921a883d1399ff15eb32384a2cc89a065105f46ce9202f481b3c", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:56853", - "10.65.0.27:56853", - "172.17.0.1:56853", - "172.18.0.1:56853", - "172.19.0.1:56853", - "172.20.0.1:56853", - "172.21.0.1:56853", - "172.22.0.1:56853", - "172.23.0.1:56853", - "172.24.0.1:56853", - "172.25.0.1:56853", - "172.27.0.1:56853", - "172.28.0.1:56853", - "172.29.0.1:56853", - "172.30.0.1:56853" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:00.131809648Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4ddd367622cc084368575c6d7d1b656e261dae5e9335999a22418e340202ba3c", - "MachineKey": "mkey:c2edfc5da989bdd4facde1ef564c6f6510086448b38f270f926a2983a7373120", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }} - }}, "charmander": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1111001440653222, - "StableID": "nmGAsB8Bg911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1111001440653222, - "Key": "nodekey:d6faab2e7e0ec2fc7bbef863fac510a7dbf2b8bf288df19543f9abc7e598ec30", - "DiscoKey": "discokey:e79dd72ab763fbaae737b31671dffa5dc10a527acd1eeb19d9455454b682225e", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:37255", - "10.65.0.27:37255", - "172.17.0.1:37255", - "172.18.0.1:37255", - "172.19.0.1:37255", - "172.20.0.1:37255", - "172.21.0.1:37255", - "172.22.0.1:37255", - "172.23.0.1:37255", - "172.24.0.1:37255", - "172.25.0.1:37255", - "172.27.0.1:37255", - "172.28.0.1:37255", - "172.29.0.1:37255", - "172.30.0.1:37255" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:53.740935778Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d6faab2e7e0ec2fc7bbef863fac510a7dbf2b8bf288df19543f9abc7e598ec30", - "MachineKey": "mkey:ae18966c2204844bc0dfebc122960d5ec5f330595da3ff641c8b9551a7001034", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1111001440653222": { - "ID": 1111001440653222, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - }}, "fearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2232659580769961, - "StableID": "nNDcmWBBSJ11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 2232659580769961, - "Key": "nodekey:0f3d03af8733585e71c3386acdabc068af290a11ad173c9dbd1d5ca2992abb23", - "DiscoKey": "discokey:dee37c53da0023d677d36f42b676d8701ad5fd8730f7faab66f4f851bf185007", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128", "10.55.0.0/16"], - "Endpoints": [ - "77.164.248.136:56599", - "10.65.0.27:56599", - "172.17.0.1:56599", - "172.18.0.1:56599", - "172.19.0.1:56599", - "172.20.0.1:56599", - "172.21.0.1:56599", - "172.22.0.1:56599", - "172.23.0.1:56599", - "172.24.0.1:56599", - "172.25.0.1:56599", - "172.27.0.1:56599", - "172.28.0.1:56599", - "172.29.0.1:56599", - "172.30.0.1:56599" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:56.380377038Z", - "Tags": ["tag:fearow"], - "PrimaryRoutes": ["10.55.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:0f3d03af8733585e71c3386acdabc068af290a11ad173c9dbd1d5ca2992abb23", - "MachineKey": "mkey:d088442e2a25a73b9a141bd39a4d887dfe23a6ff09b63cfb7f20c4dfc8f43b36", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"2232659580769961": { - "ID": 2232659580769961, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - }}, "ivysaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4890103721196372, - "StableID": "nPUBUVjjBf11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:3f948190034ff04e6a3dc51a31a607b2d1f345fe40a874a3661633927393c409", - "KeyExpiry": "2026-11-14T09:08:59Z", - "DiscoKey": "discokey:35972f831195fa374a6230409971d306f1fc1c88ccdc1130081ad357a686482d", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:55086", - "10.65.0.27:55086", - "172.17.0.1:55086", - "172.18.0.1:55086", - "172.19.0.1:55086", - "172.20.0.1:55086", - "172.21.0.1:55086", - "172.22.0.1:55086", - "172.23.0.1:55086", - "172.24.0.1:55086", - "172.25.0.1:55086", - "172.27.0.1:55086", - "172.28.0.1:55086", - "172.29.0.1:55086", - "172.30.0.1:55086" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:59.058953458Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:3f948190034ff04e6a3dc51a31a607b2d1f345fe40a874a3661633927393c409", - "MachineKey": "mkey:00fd6a4d52a5f42222dae897d0e656d17ea8102a4bf3225f34fff4635cd8e846", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - }}, "kakuna": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5343603949263262, - "StableID": "nX5hg3R8ji11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 5343603949263262, - "Key": "nodekey:50c79d4d9fa331c021289dc35df5491d972222b1cd602147bf9f697813886e5a", - "DiscoKey": "discokey:870e60d598d69eedd0bba889fcaec28b2409e57c86c32d9df63b359673eb297f", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:48644", - "10.65.0.27:48644", - "172.17.0.1:48644", - "172.18.0.1:48644", - "172.19.0.1:48644", - "172.20.0.1:48644", - "172.21.0.1:48644", - "172.22.0.1:48644", - "172.23.0.1:48644", - "172.24.0.1:48644", - "172.25.0.1:48644", - "172.27.0.1:48644", - "172.28.0.1:48644", - "172.29.0.1:48644", - "172.30.0.1:48644" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:57.995162833Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:50c79d4d9fa331c021289dc35df5491d972222b1cd602147bf9f697813886e5a", - "MachineKey": "mkey:7bdd1094f7b4491602c38e55d281a9adc220a1512618ec0d6e7e0becf7011e57", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5343603949263262": { - "ID": 5343603949263262, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - }}, "pidgeotto": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 3489568170389294, - "StableID": "nqNekE3SFU11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 3489568170389294, - "Key": "nodekey:8759e6b652900cd5327cf8daeebeff892b380c1eeee93689612df3b1a61bf71b", - "DiscoKey": "discokey:5a6562a098487689ebbde39e8529472e4667927613e593a8b589a21a38564d68", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:36194", - "10.65.0.27:36194", - "172.17.0.1:36194", - "172.18.0.1:36194", - "172.19.0.1:36194", - "172.20.0.1:36194", - "172.21.0.1:36194", - "172.22.0.1:36194", - "172.23.0.1:36194", - "172.24.0.1:36194", - "172.25.0.1:36194", - "172.27.0.1:36194", - "172.28.0.1:36194", - "172.29.0.1:36194", - "172.30.0.1:36194" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:53.175984692Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8759e6b652900cd5327cf8daeebeff892b380c1eeee93689612df3b1a61bf71b", - "MachineKey": "mkey:751f63eaa9b8fd2e1afa8601ac85a69fe2bd5b4c71b64d9e2619a950628d4f15", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"3489568170389294": { - "ID": 3489568170389294, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - }}, "pidgey": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6997798859301923, - "StableID": "npMtMyJKew11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 6997798859301923, - "Key": "nodekey:4351c29c87ef524718f06f845b1ff8beb79cd983c1c5cd4b35ea139eb55f141f", - "DiscoKey": "discokey:868bf00822d56717aabc58a15530449fcc3a0f0ec81b253ee1ce29844be8e228", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:36872", - "10.65.0.27:36872", - "172.17.0.1:36872", - "172.18.0.1:36872", - "172.19.0.1:36872", - "172.20.0.1:36872", - "172.21.0.1:36872", - "172.22.0.1:36872", - "172.23.0.1:36872", - "172.24.0.1:36872", - "172.25.0.1:36872", - "172.27.0.1:36872", - "172.28.0.1:36872", - "172.29.0.1:36872", - "172.30.0.1:36872" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:52.645416183Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4351c29c87ef524718f06f845b1ff8beb79cd983c1c5cd4b35ea139eb55f141f", - "MachineKey": "mkey:d5f643820b60fdf9d939c947a76e3a019ec03c9384c90ab1bbbdc0a82c346c46", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"6997798859301923": { - "ID": 6997798859301923, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - }}, "raticate": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6442298576950201, - "StableID": "n8QYd3GjJs11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 6442298576950201, - "Key": "nodekey:8b2455337fefa4aca2cd5c3b76969b6cb9bef01ae527f9ca3d73ecd8ed84ea20", - "DiscoKey": "discokey:70868d428d4ad928d05f6f54aab8a996997968d793cf0e14514fd4916f94a34d", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:48518", - "10.65.0.27:48518", - "172.17.0.1:48518", - "172.18.0.1:48518", - "172.19.0.1:48518", - "172.20.0.1:48518", - "172.21.0.1:48518", - "172.22.0.1:48518", - "172.23.0.1:48518", - "172.24.0.1:48518", - "172.25.0.1:48518", - "172.27.0.1:48518", - "172.28.0.1:48518", - "172.29.0.1:48518", - "172.30.0.1:48518" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:54.799700059Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8b2455337fefa4aca2cd5c3b76969b6cb9bef01ae527f9ca3d73ecd8ed84ea20", - "MachineKey": "mkey:b3a9bb89ed6795f9962795521ada67f7dcef0fbb146b2d5f4264f9daa0238b11", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"6442298576950201": { - "ID": 6442298576950201, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - }}, "rattata": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 7067747585838294, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "MachineKey": "mkey:a5683705876508cfaaae3f3875a440e784190fa1b5b4a53c77537282b8219b2d", - "Peers": [{ - "ID": 8665747210852025, - "StableID": "nWHpZ7VjfA21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e1997bcdec8da2b3320583a153b55ef42e4e433fc0defdab0f2db8cb208d680e", - "DiscoKey": "discokey:9dd4f0e3b72e928e8322334201d4688a2ba265b7bb17cb6715e83cd5d1665220", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59708", - "10.65.0.27:59708", - "172.17.0.1:59708", - "172.18.0.1:59708", - "172.19.0.1:59708", - "172.20.0.1:59708", - "172.21.0.1:59708", - "172.22.0.1:59708", - "172.23.0.1:59708", - "172.24.0.1:59708", - "172.25.0.1:59708", - "172.27.0.1:59708", - "172.28.0.1:59708", - "172.29.0.1:59708", - "172.30.0.1:59708" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:08:55.326839794Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 707081358747369, - "StableID": "nQzPP3qEX611CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a5bbb7881c8bca32b6efd73a03c65217dd8c438fa27792829382e992007f4b52", - "DiscoKey": "discokey:6887d524dd27c2f8ad4b3676851eceea715fb13e0c0db431a0be67cd594bee4e", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:44108", - "10.65.0.27:44108", - "172.17.0.1:44108", - "172.18.0.1:44108", - "172.19.0.1:44108", - "172.20.0.1:44108", - "172.21.0.1:44108", - "172.22.0.1:44108", - "172.23.0.1:44108", - "172.24.0.1:44108", - "172.25.0.1:44108", - "172.27.0.1:44108", - "172.28.0.1:44108", - "172.29.0.1:44108", - "172.30.0.1:44108" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:08:56.923114061Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "7067747585838294": { - "ID": 7067747585838294, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }} - }, "whois": {"100.64.0.13": {"Node": { - "ID": 707081358747369, - "StableID": "nQzPP3qEX611CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a5bbb7881c8bca32b6efd73a03c65217dd8c438fa27792829382e992007f4b52", - "DiscoKey": "discokey:6887d524dd27c2f8ad4b3676851eceea715fb13e0c0db431a0be67cd594bee4e", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:44108", - "10.65.0.27:44108", - "172.17.0.1:44108", - "172.18.0.1:44108", - "172.19.0.1:44108", - "172.20.0.1:44108", - "172.21.0.1:44108", - "172.22.0.1:44108", - "172.23.0.1:44108", - "172.24.0.1:44108", - "172.25.0.1:44108", - "172.27.0.1:44108", - "172.28.0.1:44108", - "172.29.0.1:44108", - "172.30.0.1:44108" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:08:56.923114061Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "100.64.0.9": {"Node": { - "ID": 8665747210852025, - "StableID": "nWHpZ7VjfA21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e1997bcdec8da2b3320583a153b55ef42e4e433fc0defdab0f2db8cb208d680e", - "DiscoKey": "discokey:9dd4f0e3b72e928e8322334201d4688a2ba265b7bb17cb6715e83cd5d1665220", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59708", - "10.65.0.27:59708", - "172.17.0.1:59708", - "172.18.0.1:59708", - "172.19.0.1:59708", - "172.20.0.1:59708", - "172.21.0.1:59708", - "172.22.0.1:59708", - "172.23.0.1:59708", - "172.24.0.1:59708", - "172.25.0.1:59708", - "172.27.0.1:59708", - "172.28.0.1:59708", - "172.29.0.1:59708", - "172.30.0.1:59708" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:08:55.326839794Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::9": {"Node": { - "ID": 8665747210852025, - "StableID": "nWHpZ7VjfA21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e1997bcdec8da2b3320583a153b55ef42e4e433fc0defdab0f2db8cb208d680e", - "DiscoKey": "discokey:9dd4f0e3b72e928e8322334201d4688a2ba265b7bb17cb6715e83cd5d1665220", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59708", - "10.65.0.27:59708", - "172.17.0.1:59708", - "172.18.0.1:59708", - "172.19.0.1:59708", - "172.20.0.1:59708", - "172.21.0.1:59708", - "172.22.0.1:59708", - "172.23.0.1:59708", - "172.24.0.1:59708", - "172.25.0.1:59708", - "172.27.0.1:59708", - "172.28.0.1:59708", - "172.29.0.1:59708", - "172.30.0.1:59708" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:08:55.326839794Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::d": {"Node": { - "ID": 707081358747369, - "StableID": "nQzPP3qEX611CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a5bbb7881c8bca32b6efd73a03c65217dd8c438fa27792829382e992007f4b52", - "DiscoKey": "discokey:6887d524dd27c2f8ad4b3676851eceea715fb13e0c0db431a0be67cd594bee4e", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:44108", - "10.65.0.27:44108", - "172.17.0.1:44108", - "172.18.0.1:44108", - "172.19.0.1:44108", - "172.20.0.1:44108", - "172.21.0.1:44108", - "172.22.0.1:44108", - "172.23.0.1:44108", - "172.24.0.1:44108", - "172.25.0.1:44108", - "172.27.0.1:44108", - "172.28.0.1:44108", - "172.29.0.1:44108", - "172.30.0.1:44108" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:08:56.923114061Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "spearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 668394826740252, - "StableID": "nuoe2jbiD611CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 668394826740252, - "Key": "nodekey:6c1416b431ea96423daddbeb1a8e0c83678d2324ddc53e2dcc2c0515fcd07570", - "DiscoKey": "discokey:28e6b1477dca8bedaed0f887e145954f56aa0cc7972ef743f475c54a9557722d", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128", "10.44.0.0/16"], - "Endpoints": [ - "77.164.248.136:38545", - "10.65.0.27:38545", - "172.17.0.1:38545", - "172.18.0.1:38545", - "172.19.0.1:38545", - "172.20.0.1:38545", - "172.21.0.1:38545", - "172.22.0.1:38545", - "172.23.0.1:38545", - "172.24.0.1:38545", - "172.25.0.1:38545", - "172.27.0.1:38545", - "172.28.0.1:38545", - "172.29.0.1:38545", - "172.30.0.1:38545" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:55.85676883Z", - "Tags": ["tag:spearow"], - "PrimaryRoutes": ["10.44.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:6c1416b431ea96423daddbeb1a8e0c83678d2324ddc53e2dcc2c0515fcd07570", - "MachineKey": "mkey:fac25f80e5a9463dfd40bbd74fda72ea0e9f0ba111a3ebeeb9a18b5cc70ca108", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"668394826740252": { - "ID": 668394826740252, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - }}, "squirtle": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 707081358747369, - "StableID": "nQzPP3qEX611CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 707081358747369, - "Key": "nodekey:a5bbb7881c8bca32b6efd73a03c65217dd8c438fa27792829382e992007f4b52", - "DiscoKey": "discokey:6887d524dd27c2f8ad4b3676851eceea715fb13e0c0db431a0be67cd594bee4e", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:44108", - "10.65.0.27:44108", - "172.17.0.1:44108", - "172.18.0.1:44108", - "172.19.0.1:44108", - "172.20.0.1:44108", - "172.21.0.1:44108", - "172.22.0.1:44108", - "172.23.0.1:44108", - "172.24.0.1:44108", - "172.25.0.1:44108", - "172.27.0.1:44108", - "172.28.0.1:44108", - "172.29.0.1:44108", - "172.30.0.1:44108" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:08:56.923114061Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a5bbb7881c8bca32b6efd73a03c65217dd8c438fa27792829382e992007f4b52", - "MachineKey": "mkey:f96acb2bd50905ee969a5c746012d94f96ce8a4476227930097e0e2f42515d42", - "Peers": [{ - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.33.5.0/24", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "707081358747369": { - "ID": 707081358747369, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 7067747585838294, - "StableID": "nyaAoAkzBx11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f0f21941bc3aee295900c96b04335b18d1527061e6eaf773c8e56b0cec38d47f", - "DiscoKey": "discokey:9e1d242539c3dd067ad5eb339aacb8a13da29ae57c218f9e91f773b7a2e7ca6e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45427", - "10.65.0.27:45427", - "172.17.0.1:45427", - "172.18.0.1:45427", - "172.19.0.1:45427", - "172.20.0.1:45427", - "172.21.0.1:45427", - "172.22.0.1:45427", - "172.23.0.1:45427", - "172.24.0.1:45427", - "172.25.0.1:45427", - "172.27.0.1:45427", - "172.28.0.1:45427", - "172.29.0.1:45427", - "172.30.0.1:45427" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:08:54.27787786Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "venusaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 7881217227059504, - "StableID": "nywtPKDRY421CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:c713605ecf9f8727a3a302dcecad491e7cb60b4fe1a2084b05e3c6cf82c5a83b", - "KeyExpiry": "2026-11-14T09:08:59Z", - "DiscoKey": "discokey:c1664318ec9349ed48ce1530ec3244ccbe891aaee31bba74450bc06e61048713", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:49496", - "10.65.0.27:49496", - "172.17.0.1:49496", - "172.18.0.1:49496", - "172.19.0.1:49496", - "172.20.0.1:49496", - "172.21.0.1:49496", - "172.22.0.1:49496", - "172.23.0.1:49496", - "172.24.0.1:49496", - "172.25.0.1:49496", - "172.27.0.1:49496", - "172.28.0.1:49496", - "172.29.0.1:49496", - "172.30.0.1:49496" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:59.597175811Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c713605ecf9f8727a3a302dcecad491e7cb60b4fe1a2084b05e3c6cf82c5a83b", - "MachineKey": "mkey:23363497a57674b8424ed254203df2e560a97c9982c0dea8674b5c20b002f24a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }} - }}, "weedle": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8774068181880326, - "StableID": "nK3BrntnWB21CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 8774068181880326, - "Key": "nodekey:982431d8795a4558c52df10d87f17ad5831dee9b8c0fa715658d34389d4ffe67", - "DiscoKey": "discokey:1c9023924d510ca77d8566ee1ab23733512c58d4ed073c44a256e37cd984eb37", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:54831", - "10.65.0.27:54831", - "172.17.0.1:54831", - "172.18.0.1:54831", - "172.19.0.1:54831", - "172.20.0.1:54831", - "172.21.0.1:54831", - "172.22.0.1:54831", - "172.23.0.1:54831", - "172.24.0.1:54831", - "172.25.0.1:54831", - "172.27.0.1:54831", - "172.28.0.1:54831", - "172.29.0.1:54831", - "172.30.0.1:54831" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:08:57.461846474Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:982431d8795a4558c52df10d87f17ad5831dee9b8c0fa715658d34389d4ffe67", - "MachineKey": "mkey:c7a43d8ce9185f44c8b3263e950203728450ef77a3cca16450dead4115626479", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8774068181880326": { - "ID": 8774068181880326, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - }}} -} diff --git a/hscontrol/policy/v2/testdata/grant_results/via-grant-v49.hujson b/hscontrol/policy/v2/testdata/grant_results/via-grant-v49.hujson deleted file mode 100644 index f3c82c0ed..000000000 --- a/hscontrol/policy/v2/testdata/grant_results/via-grant-v49.hujson +++ /dev/null @@ -1,14164 +0,0 @@ -// via-grant-v49 -// -// via grant v49 dst host alias broader than advertised subnet -// -// Nodes with filter rules: 2 of 15 -// Captured at: 2026-05-18T09:09:45Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "via-grant-v49", - "description": "via grant v49 dst host alias broader than advertised subnet", - "category": "grant", - "captured_at": "2026-05-18T09:09:45.383298229Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n \n \n \n \n \n{\n\t\"category\": \"grant\",\n\t\"description\": \"via grant v49 dst host alias broader than advertised subnet\",\n\t\"id\": \"via-grant-v49\",\n\t\"options\": {\"capture_whois\": true},\n\t\"policy\": {\"autoApprovers\": {\"routes\": {\n\t\t\"10.33.0.0/16\": [\"tag:router\"],\n\t\t\"10.44.0.0/16\": [\"tag:spearow\"],\n\t\t\"10.55.0.0/16\": [\"tag:fearow\"]\n\t}}, \"grants\": [{\n\t\t\"dst\": [\"internal\"],\n\t\t\"ip\": [\"*\"],\n\t\t\"src\": [\"tag:group-a\"],\n\t\t\"via\": [\"tag:router\"]\n\t}], \"groups\": {\n\t\t\"group:admins\": [\"odin@example.com\"],\n\t\t\"group:developers\": [\"thor@example.org\", \"odin@example.com\"],\n\t\t\"group:empty\": [],\n\t\t\"group:monitors\": [\"freya@example.com\"]\n\t}, \"hosts\": {\n\t\t\"internal\": \"10.0.0.0/8\",\n\t\t\"prodbox\": \"100.103.8.15\",\n\t\t\"webserver\": \"100.108.74.26\"\n\t}, \"tagOwners\": {\n\t\t\"tag:client\": [\"odin@example.com\"],\n\t\t\"tag:exit\": [\"odin@example.com\"],\n\t\t\"tag:pidgey\": [\"odin@example.com\"],\n\t\t\"tag:pidgeotto\": [\"odin@example.com\"],\n\t\t\"tag:group-a\": [\"odin@example.com\"],\n\t\t\"tag:group-b\": [\"odin@example.com\"],\n\t\t\"tag:ha\": [\"odin@example.com\"],\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:router\": [\"odin@example.com\"],\n\t\t\"tag:spearow\": [\"odin@example.com\"],\n\t\t\"tag:fearow\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/grant/via-grant-v49.hujson", - "full_policy": {"autoApprovers": {"routes": { - "10.33.0.0/16": ["tag:router"], - "10.44.0.0/16": ["tag:spearow"], - "10.55.0.0/16": ["tag:fearow"] - }}, "grants": [{ - "dst": ["internal"], - "ip": ["*"], - "src": ["tag:group-a"], - "via": ["tag:router"] - }], "groups": { - "group:admins": ["odin@example.com"], - "group:developers": ["thor@example.org", "odin@example.com"], - "group:empty": [], - "group:monitors": ["freya@example.com"] - }, "hosts": { - "internal": "10.0.0.0/8", - "prodbox": "100.103.8.15", - "webserver": "100.108.74.26" - }, "tagOwners": { - "tag:client": ["odin@example.com"], - "tag:exit": ["odin@example.com"], - "tag:pidgey": ["odin@example.com"], - "tag:pidgeotto": ["odin@example.com"], - "tag:group-a": ["odin@example.com"], - "tag:group-b": ["odin@example.com"], - "tag:ha": ["odin@example.com"], - "tag:prod": ["odin@example.com"], - "tag:router": ["odin@example.com"], - "tag:spearow": ["odin@example.com"], - "tag:fearow": ["odin@example.com"], - "tag:server": ["odin@example.com"] - }} - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": ["10.33.0.0/16"] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": ["10.55.0.0/16"] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": ["10.44.0.0/16"] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": ["10.33.0.0/16"] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1654201741972838, - "StableID": "nsHePL5CvD11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1654201741972838, - "Key": "nodekey:2f6ed2755d5bec53ed1d463206d6e4f324bd49285205161348efd4578829723d", - "DiscoKey": "discokey:cd8c9fc6fc3c67d1d5a85cca3e9d9c305276819513e0ed87f351f24507f3cc2f", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:46172", - "10.65.0.27:46172", - "172.17.0.1:46172", - "172.18.0.1:46172", - "172.19.0.1:46172", - "172.20.0.1:46172", - "172.21.0.1:46172", - "172.22.0.1:46172", - "172.23.0.1:46172", - "172.24.0.1:46172", - "172.25.0.1:46172", - "172.27.0.1:46172", - "172.28.0.1:46172", - "172.29.0.1:46172", - "172.30.0.1:46172" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:57.643616429Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:2f6ed2755d5bec53ed1d463206d6e4f324bd49285205161348efd4578829723d", - "MachineKey": "mkey:0e5fded7ceb7f42dc07075f281606ae8adaf5261bbd54e6894c43c51d3821114", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1654201741972838": { - "ID": 1654201741972838, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }}, "blastoise": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4353680609859707, - "StableID": "ncdq1ponza11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 4353680609859707, - "Key": "nodekey:b5db4038b4b10452989f805261c321d951a289c50fc7ee9e119cc43f3b60133a", - "DiscoKey": "discokey:e5ac5dd18c7f96c6189e8c8a377a3bbfc606c1520a4627b8ce97c71c7ce9cc7f", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59178", - "10.65.0.27:59178", - "172.17.0.1:59178", - "172.18.0.1:59178", - "172.19.0.1:59178", - "172.20.0.1:59178", - "172.21.0.1:59178", - "172.22.0.1:59178", - "172.23.0.1:59178", - "172.24.0.1:59178", - "172.25.0.1:59178", - "172.27.0.1:59178", - "172.28.0.1:59178", - "172.29.0.1:59178", - "172.30.0.1:59178" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:54.427828038Z", - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b5db4038b4b10452989f805261c321d951a289c50fc7ee9e119cc43f3b60133a", - "MachineKey": "mkey:e648ae1613f7d9dc9484ac10ddcb78bb2e34a44b6895b1a830e9f17277d0415b", - "Peers": [{ - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "4353680609859707": { - "ID": 4353680609859707, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "bulbasaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 926567223465290, - "StableID": "n7w7fHMeE811CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:ef6bd76a781c32ae5017c044987675e837068daa77b0699df609f533b6df5a57", - "KeyExpiry": "2026-11-14T09:09:59Z", - "DiscoKey": "discokey:9cf87cc83bb0273a8801ace89d2bd27542395a98924482edd63540db1f6ece35", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:51843", - "10.65.0.27:51843", - "172.17.0.1:51843", - "172.18.0.1:51843", - "172.19.0.1:51843", - "172.20.0.1:51843", - "172.21.0.1:51843", - "172.22.0.1:51843", - "172.23.0.1:51843", - "172.24.0.1:51843", - "172.25.0.1:51843", - "172.27.0.1:51843", - "172.28.0.1:51843", - "172.29.0.1:51843", - "172.30.0.1:51843" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:59.241340908Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ef6bd76a781c32ae5017c044987675e837068daa77b0699df609f533b6df5a57", - "MachineKey": "mkey:9f406d8cdb4b07d8938287577dc41ef14cdd5663861ac77d0c012a2093b04958", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }} - }}, "charmander": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1154714393061985, - "StableID": "nJ1KnZPy1A11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1154714393061985, - "Key": "nodekey:8729c0c3cb115278d3a6503c1671517189d8db5c14b1593d49127307f31d8d1d", - "DiscoKey": "discokey:39093f5aa31169f5dc3208fe0cb7a1bcca0c67fd5a6d3ad3ff93656b33d4b412", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:50136", - "10.65.0.27:50136", - "172.17.0.1:50136", - "172.18.0.1:50136", - "172.19.0.1:50136", - "172.20.0.1:50136", - "172.21.0.1:50136", - "172.22.0.1:50136", - "172.23.0.1:50136", - "172.24.0.1:50136", - "172.25.0.1:50136", - "172.27.0.1:50136", - "172.28.0.1:50136", - "172.29.0.1:50136", - "172.30.0.1:50136" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:52.838243604Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8729c0c3cb115278d3a6503c1671517189d8db5c14b1593d49127307f31d8d1d", - "MachineKey": "mkey:62bfd0315ccbbcfedb7b22991a2dcee12f458304a340f7f931992d9d37641265", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1154714393061985": { - "ID": 1154714393061985, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - }}, "fearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6512658328578833, - "StableID": "n2frwTVbrs11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 6512658328578833, - "Key": "nodekey:eacb11c6f0c205ec9f4f7b3694554ab846c9fab4582b9b8a6510310626d41c7c", - "DiscoKey": "discokey:fbc334bc4e07db567cd5605bf03951d67209334c38ff7690bd0d1e118cfc0028", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128", "10.55.0.0/16"], - "Endpoints": [ - "77.164.248.136:60774", - "10.65.0.27:60774", - "172.17.0.1:60774", - "172.18.0.1:60774", - "172.19.0.1:60774", - "172.20.0.1:60774", - "172.21.0.1:60774", - "172.22.0.1:60774", - "172.23.0.1:60774", - "172.24.0.1:60774", - "172.25.0.1:60774", - "172.27.0.1:60774", - "172.28.0.1:60774", - "172.29.0.1:60774", - "172.30.0.1:60774" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:55.50305827Z", - "Tags": ["tag:fearow"], - "PrimaryRoutes": ["10.55.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:eacb11c6f0c205ec9f4f7b3694554ab846c9fab4582b9b8a6510310626d41c7c", - "MachineKey": "mkey:e550a5efb38cf93e01a19c06a53485d519152ffa932c982fba6003655b0fee10", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"6512658328578833": { - "ID": 6512658328578833, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - }}, "ivysaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8517488899238478, - "StableID": "n3boFSzaW921CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:6351d6da190536fb1dcdbaef6045950abdbee90284bb5e0d358b81fbc76a8f7e", - "KeyExpiry": "2026-11-14T09:09:58Z", - "DiscoKey": "discokey:a726a69088fc615c8da8473e572bbda47d26c2e452c2062252c8c582f7dbc70c", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:35182", - "10.65.0.27:35182", - "172.17.0.1:35182", - "172.18.0.1:35182", - "172.19.0.1:35182", - "172.20.0.1:35182", - "172.21.0.1:35182", - "172.22.0.1:35182", - "172.23.0.1:35182", - "172.24.0.1:35182", - "172.25.0.1:35182", - "172.27.0.1:35182", - "172.28.0.1:35182", - "172.29.0.1:35182", - "172.30.0.1:35182" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:58.170587249Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:6351d6da190536fb1dcdbaef6045950abdbee90284bb5e0d358b81fbc76a8f7e", - "MachineKey": "mkey:47a38b805622722b150cf68bbdcae6bc9326b146290f477356a3c5bdcea55a0c", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - }}, "kakuna": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4398905709499259, - "StableID": "nGfoC3oGMb11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 4398905709499259, - "Key": "nodekey:b155c51fc9f7601ea6dec78b67cf298b3713fc02417b530bea8335274bc1ad76", - "DiscoKey": "discokey:f19ef368e77535b2d3a4a8d147decc80d4ea61b0abea57b03b0fd74c6ce79111", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:56640", - "10.65.0.27:56640", - "172.17.0.1:56640", - "172.18.0.1:56640", - "172.19.0.1:56640", - "172.20.0.1:56640", - "172.21.0.1:56640", - "172.22.0.1:56640", - "172.23.0.1:56640", - "172.24.0.1:56640", - "172.25.0.1:56640", - "172.27.0.1:56640", - "172.28.0.1:56640", - "172.29.0.1:56640", - "172.30.0.1:56640" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:57.112792896Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b155c51fc9f7601ea6dec78b67cf298b3713fc02417b530bea8335274bc1ad76", - "MachineKey": "mkey:46016eeabfdc23062f5b9b92c6e103d8f682b22ab22bf04de243af1433d95e0d", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4398905709499259": { - "ID": 4398905709499259, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - }}, "pidgeotto": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1462701867398957, - "StableID": "n8vcEUhTRC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1462701867398957, - "Key": "nodekey:d2e24d6575449f6f7f05da050cd456eea796a70fecf8f5bca2b90b981b708e64", - "DiscoKey": "discokey:b9247209ed5489c8a97a9e4aaceb3a45db0bfbfe641940cd96aca8dce12a3619", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:49323", - "10.65.0.27:49323", - "172.17.0.1:49323", - "172.18.0.1:49323", - "172.19.0.1:49323", - "172.20.0.1:49323", - "172.21.0.1:49323", - "172.22.0.1:49323", - "172.23.0.1:49323", - "172.24.0.1:49323", - "172.25.0.1:49323", - "172.27.0.1:49323", - "172.28.0.1:49323", - "172.29.0.1:49323", - "172.30.0.1:49323" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:52.330166848Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d2e24d6575449f6f7f05da050cd456eea796a70fecf8f5bca2b90b981b708e64", - "MachineKey": "mkey:48562c45554f387ac4eb430f5eec7013fad827d43c783adc210d1afc32f2142a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1462701867398957": { - "ID": 1462701867398957, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - }}, "pidgey": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1421890685488846, - "StableID": "nRGmq7fy6C11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1421890685488846, - "Key": "nodekey:c8fcfbdfc7940b880da1345f4b9d4308d4206d8e8b67d730f6302a0c6e6f1d3f", - "DiscoKey": "discokey:6e5ea488a1e1ad360f3e23b92cd1250847f52d8fe9d9c528a4cd30fa046ca922", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:59725", - "10.65.0.27:59725", - "172.17.0.1:59725", - "172.18.0.1:59725", - "172.19.0.1:59725", - "172.20.0.1:59725", - "172.21.0.1:59725", - "172.22.0.1:59725", - "172.23.0.1:59725", - "172.24.0.1:59725", - "172.25.0.1:59725", - "172.27.0.1:59725", - "172.28.0.1:59725", - "172.29.0.1:59725", - "172.30.0.1:59725" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:51.783297044Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c8fcfbdfc7940b880da1345f4b9d4308d4206d8e8b67d730f6302a0c6e6f1d3f", - "MachineKey": "mkey:7f1d861dba06f6b5d10c8e20bef0038714f88c3fb9d6e4dab0c89815eb1ca01f", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1421890685488846": { - "ID": 1421890685488846, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - }}, "raticate": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 6830479516309268, - "StableID": "nm1jyd7YLv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 6830479516309268, - "Key": "nodekey:c4d281f165fd2867c4d67011cd71701fa9be987348ddb36641109f9a8caea561", - "DiscoKey": "discokey:a2a38ca3a8d76366a4649f5219b7e36c23d9d449188acd52da16b31490ef2161", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:34630", - "10.65.0.27:34630", - "172.17.0.1:34630", - "172.18.0.1:34630", - "172.19.0.1:34630", - "172.20.0.1:34630", - "172.21.0.1:34630", - "172.22.0.1:34630", - "172.23.0.1:34630", - "172.24.0.1:34630", - "172.25.0.1:34630", - "172.27.0.1:34630", - "172.28.0.1:34630", - "172.29.0.1:34630", - "172.30.0.1:34630" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:53.9152509Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c4d281f165fd2867c4d67011cd71701fa9be987348ddb36641109f9a8caea561", - "MachineKey": "mkey:9b49218879ebcd1bf07a0b140e7bed1b19850e5bec7adb590ee8dfd54ac4ca6e", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"6830479516309268": { - "ID": 6830479516309268, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - }}, "rattata": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1191708573799268, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "MachineKey": "mkey:91230ff01950c8ec552b951556bdac624112f0816e7ca2e675bf2cd99d885c5d", - "Peers": [{ - "ID": 4353680609859707, - "StableID": "ncdq1ponza11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5db4038b4b10452989f805261c321d951a289c50fc7ee9e119cc43f3b60133a", - "DiscoKey": "discokey:e5ac5dd18c7f96c6189e8c8a377a3bbfc606c1520a4627b8ce97c71c7ce9cc7f", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59178", - "10.65.0.27:59178", - "172.17.0.1:59178", - "172.18.0.1:59178", - "172.19.0.1:59178", - "172.20.0.1:59178", - "172.21.0.1:59178", - "172.22.0.1:59178", - "172.23.0.1:59178", - "172.24.0.1:59178", - "172.25.0.1:59178", - "172.27.0.1:59178", - "172.28.0.1:59178", - "172.29.0.1:59178", - "172.30.0.1:59178" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:09:54.427828038Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5736597902827947, - "StableID": "nJ8kcZh7om11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a82e54f5c6150a0caa9610e9287570a51cad64af696ff4432b5545f7590e804c", - "DiscoKey": "discokey:105764c326fdac03402af2449523a6be028f244a3bbf9bbad5c6b5114fb1d11c", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:59206", - "10.65.0.27:59206", - "172.17.0.1:59206", - "172.18.0.1:59206", - "172.19.0.1:59206", - "172.20.0.1:59206", - "172.21.0.1:59206", - "172.22.0.1:59206", - "172.23.0.1:59206", - "172.24.0.1:59206", - "172.25.0.1:59206", - "172.27.0.1:59206", - "172.28.0.1:59206", - "172.29.0.1:59206", - "172.30.0.1:59206" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:09:56.03671269Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1191708573799268": { - "ID": 1191708573799268, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }, "1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }} - }, "whois": {"100.64.0.13": {"Node": { - "ID": 5736597902827947, - "StableID": "nJ8kcZh7om11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a82e54f5c6150a0caa9610e9287570a51cad64af696ff4432b5545f7590e804c", - "DiscoKey": "discokey:105764c326fdac03402af2449523a6be028f244a3bbf9bbad5c6b5114fb1d11c", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:59206", - "10.65.0.27:59206", - "172.17.0.1:59206", - "172.18.0.1:59206", - "172.19.0.1:59206", - "172.20.0.1:59206", - "172.21.0.1:59206", - "172.22.0.1:59206", - "172.23.0.1:59206", - "172.24.0.1:59206", - "172.25.0.1:59206", - "172.27.0.1:59206", - "172.28.0.1:59206", - "172.29.0.1:59206", - "172.30.0.1:59206" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:09:56.03671269Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "100.64.0.9": {"Node": { - "ID": 4353680609859707, - "StableID": "ncdq1ponza11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5db4038b4b10452989f805261c321d951a289c50fc7ee9e119cc43f3b60133a", - "DiscoKey": "discokey:e5ac5dd18c7f96c6189e8c8a377a3bbfc606c1520a4627b8ce97c71c7ce9cc7f", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59178", - "10.65.0.27:59178", - "172.17.0.1:59178", - "172.18.0.1:59178", - "172.19.0.1:59178", - "172.20.0.1:59178", - "172.21.0.1:59178", - "172.22.0.1:59178", - "172.23.0.1:59178", - "172.24.0.1:59178", - "172.25.0.1:59178", - "172.27.0.1:59178", - "172.28.0.1:59178", - "172.29.0.1:59178", - "172.30.0.1:59178" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:09:54.427828038Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::9": {"Node": { - "ID": 4353680609859707, - "StableID": "ncdq1ponza11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5db4038b4b10452989f805261c321d951a289c50fc7ee9e119cc43f3b60133a", - "DiscoKey": "discokey:e5ac5dd18c7f96c6189e8c8a377a3bbfc606c1520a4627b8ce97c71c7ce9cc7f", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59178", - "10.65.0.27:59178", - "172.17.0.1:59178", - "172.18.0.1:59178", - "172.19.0.1:59178", - "172.20.0.1:59178", - "172.21.0.1:59178", - "172.22.0.1:59178", - "172.23.0.1:59178", - "172.24.0.1:59178", - "172.25.0.1:59178", - "172.27.0.1:59178", - "172.28.0.1:59178", - "172.29.0.1:59178", - "172.30.0.1:59178" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-18T09:09:54.427828038Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::d": {"Node": { - "ID": 5736597902827947, - "StableID": "nJ8kcZh7om11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a82e54f5c6150a0caa9610e9287570a51cad64af696ff4432b5545f7590e804c", - "DiscoKey": "discokey:105764c326fdac03402af2449523a6be028f244a3bbf9bbad5c6b5114fb1d11c", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:59206", - "10.65.0.27:59206", - "172.17.0.1:59206", - "172.18.0.1:59206", - "172.19.0.1:59206", - "172.20.0.1:59206", - "172.21.0.1:59206", - "172.22.0.1:59206", - "172.23.0.1:59206", - "172.24.0.1:59206", - "172.25.0.1:59206", - "172.27.0.1:59206", - "172.28.0.1:59206", - "172.29.0.1:59206", - "172.30.0.1:59206" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-18T09:09:56.03671269Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "spearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5554457969945969, - "StableID": "nNHUT9CdNk11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 5554457969945969, - "Key": "nodekey:e85ccf9890a697a7faf59cddf1944fa7cf88f28eb8f8cede6d456812a815856d", - "DiscoKey": "discokey:c50b6000f3e9a04b6cc80199dbb9293fed0b048fcccdcf2d50dd3ae242407243", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128", "10.44.0.0/16"], - "Endpoints": [ - "77.164.248.136:32906", - "10.65.0.27:32906", - "172.17.0.1:32906", - "172.18.0.1:32906", - "172.19.0.1:32906", - "172.20.0.1:32906", - "172.21.0.1:32906", - "172.22.0.1:32906", - "172.23.0.1:32906", - "172.24.0.1:32906", - "172.25.0.1:32906", - "172.27.0.1:32906", - "172.28.0.1:32906", - "172.29.0.1:32906", - "172.30.0.1:32906" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:54.973023641Z", - "Tags": ["tag:spearow"], - "PrimaryRoutes": ["10.44.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e85ccf9890a697a7faf59cddf1944fa7cf88f28eb8f8cede6d456812a815856d", - "MachineKey": "mkey:e5f41d0ea32bd6e75e786f68a93db26c678770bd32e93590053318e48e211a16", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5554457969945969": { - "ID": 5554457969945969, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - }}, "squirtle": {"packet_filter_rules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], "packet_filter_matches": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5736597902827947, - "StableID": "nJ8kcZh7om11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 5736597902827947, - "Key": "nodekey:a82e54f5c6150a0caa9610e9287570a51cad64af696ff4432b5545f7590e804c", - "DiscoKey": "discokey:105764c326fdac03402af2449523a6be028f244a3bbf9bbad5c6b5114fb1d11c", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:59206", - "10.65.0.27:59206", - "172.17.0.1:59206", - "172.18.0.1:59206", - "172.19.0.1:59206", - "172.20.0.1:59206", - "172.21.0.1:59206", - "172.22.0.1:59206", - "172.23.0.1:59206", - "172.24.0.1:59206", - "172.25.0.1:59206", - "172.27.0.1:59206", - "172.28.0.1:59206", - "172.29.0.1:59206", - "172.30.0.1:59206" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:09:56.03671269Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a82e54f5c6150a0caa9610e9287570a51cad64af696ff4432b5545f7590e804c", - "MachineKey": "mkey:eb56e1bc39a518091833374735bbab511a36e3db348ce585e57e5716399a931c", - "Peers": [{ - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [6, 17, 1, 58], - "Srcs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "SrcCaps": null, - "Dsts": [{"Net": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.64.0.5", "fd7a:115c:a1e0::5"], - "DstPorts": [{"IP": "10.0.0.0/8", "Ports": {"First": 0, "Last": 65535}}] - }], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "5736597902827947": { - "ID": 5736597902827947, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - }, "whois": {"100.64.0.5": {"Node": { - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::5": {"Node": { - "ID": 1191708573799268, - "StableID": "nFJSrUAjJA11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38a866479caef0d5c0e853c85a8a3df39f1fd96c005b32071b40f4ef80d6ec13", - "DiscoKey": "discokey:d23472be07dfe7f1384c1d713569b6abc53d6c3a8ea28b723a8265a421e7cf59", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60165", - "10.65.0.27:60165", - "172.17.0.1:60165", - "172.18.0.1:60165", - "172.19.0.1:60165", - "172.20.0.1:60165", - "172.21.0.1:60165", - "172.22.0.1:60165", - "172.23.0.1:60165", - "172.24.0.1:60165", - "172.25.0.1:60165", - "172.27.0.1:60165", - "172.28.0.1:60165", - "172.29.0.1:60165", - "172.30.0.1:60165" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-18T09:09:53.365800595Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "venusaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8565173068968397, - "StableID": "nSmk3DaBt921CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:8d5be578b9466b6c25398b6a8cec4dfa921bb81653c26c09c6602d3335b7292f", - "KeyExpiry": "2026-11-14T09:09:58Z", - "DiscoKey": "discokey:b24c6d83ddffffa2ef75fc0ec43f7d992823795f4a07739c5e761f62f2125e73", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:54738", - "10.65.0.27:54738", - "172.17.0.1:54738", - "172.18.0.1:54738", - "172.19.0.1:54738", - "172.20.0.1:54738", - "172.21.0.1:54738", - "172.22.0.1:54738", - "172.23.0.1:54738", - "172.24.0.1:54738", - "172.25.0.1:54738", - "172.27.0.1:54738", - "172.28.0.1:54738", - "172.29.0.1:54738", - "172.30.0.1:54738" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:58.707802059Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8d5be578b9466b6c25398b6a8cec4dfa921bb81653c26c09c6602d3335b7292f", - "MachineKey": "mkey:aee00888869f777b2ca1c5eb63fb3333c9ee79fede7503c79ba3c0a182a74f1a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }} - }}, "weedle": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5904929113215889, - "StableID": "nGAoFYUM7o11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 5904929113215889, - "Key": "nodekey:f3edbda487f65b4b5f396a8e5fb4ac2c5b81438b86499ee2660f3264080dff52", - "DiscoKey": "discokey:1f5cbc97dcd670225e8a75a5b4a39bdedfd929f95d1ef6ace18a26e5ef7c7741", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:43137", - "10.65.0.27:43137", - "172.17.0.1:43137", - "172.18.0.1:43137", - "172.19.0.1:43137", - "172.20.0.1:43137", - "172.21.0.1:43137", - "172.22.0.1:43137", - "172.23.0.1:43137", - "172.24.0.1:43137", - "172.25.0.1:43137", - "172.27.0.1:43137", - "172.28.0.1:43137", - "172.29.0.1:43137", - "172.30.0.1:43137" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:09:56.578084606Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f3edbda487f65b4b5f396a8e5fb4ac2c5b81438b86499ee2660f3264080dff52", - "MachineKey": "mkey:dfc7fdac891e1d2343138400540e6f94c32bd2fe6d74abf31f52b9e4b11d6976", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5904929113215889": { - "ID": 5904929113215889, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - }}} -} diff --git a/hscontrol/policy/v2/testdata/grant_results/via-grant-v50.hujson b/hscontrol/policy/v2/testdata/grant_results/via-grant-v50.hujson deleted file mode 100644 index c4cf73770..000000000 --- a/hscontrol/policy/v2/testdata/grant_results/via-grant-v50.hujson +++ /dev/null @@ -1,13635 +0,0 @@ -// via-grant-v50 -// -// via grant v50 dst cidr disjoint from advertised subnet -// Captured at: 2026-05-18T09:10:44Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "via-grant-v50", - "description": "via grant v50 dst cidr disjoint from advertised subnet", - "category": "grant", - "captured_at": "2026-05-18T09:10:44.446555363Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n \n \n \n \n{\n\t\"category\": \"grant\",\n\t\"description\": \"via grant v50 dst cidr disjoint from advertised subnet\",\n\t\"id\": \"via-grant-v50\",\n\t\"options\": {\"capture_whois\": true},\n\t\"policy\": {\"autoApprovers\": {\"routes\": {\n\t\t\"10.33.0.0/16\": [\"tag:router\"],\n\t\t\"10.44.0.0/16\": [\"tag:spearow\"],\n\t\t\"10.55.0.0/16\": [\"tag:fearow\"]\n\t}}, \"grants\": [{\n\t\t\"dst\": [\"192.168.0.0/16\"],\n\t\t\"ip\": [\"*\"],\n\t\t\"src\": [\"tag:group-a\"],\n\t\t\"via\": [\"tag:router\"]\n\t}], \"groups\": {\n\t\t\"group:admins\": [\"odin@example.com\"],\n\t\t\"group:developers\": [\"thor@example.org\", \"odin@example.com\"],\n\t\t\"group:empty\": [],\n\t\t\"group:monitors\": [\"freya@example.com\"]\n\t}, \"hosts\": {\n\t\t\"internal\": \"10.0.0.0/8\",\n\t\t\"prodbox\": \"100.103.8.15\",\n\t\t\"webserver\": \"100.108.74.26\"\n\t}, \"tagOwners\": {\n\t\t\"tag:client\": [\"odin@example.com\"],\n\t\t\"tag:exit\": [\"odin@example.com\"],\n\t\t\"tag:pidgey\": [\"odin@example.com\"],\n\t\t\"tag:pidgeotto\": [\"odin@example.com\"],\n\t\t\"tag:group-a\": [\"odin@example.com\"],\n\t\t\"tag:group-b\": [\"odin@example.com\"],\n\t\t\"tag:ha\": [\"odin@example.com\"],\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:router\": [\"odin@example.com\"],\n\t\t\"tag:spearow\": [\"odin@example.com\"],\n\t\t\"tag:fearow\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/grant/via-grant-v50.hujson", - "full_policy": {"autoApprovers": {"routes": { - "10.33.0.0/16": ["tag:router"], - "10.44.0.0/16": ["tag:spearow"], - "10.55.0.0/16": ["tag:fearow"] - }}, "grants": [{ - "dst": ["192.168.0.0/16"], - "ip": ["*"], - "src": ["tag:group-a"], - "via": ["tag:router"] - }], "groups": { - "group:admins": ["odin@example.com"], - "group:developers": ["thor@example.org", "odin@example.com"], - "group:empty": [], - "group:monitors": ["freya@example.com"] - }, "hosts": { - "internal": "10.0.0.0/8", - "prodbox": "100.103.8.15", - "webserver": "100.108.74.26" - }, "tagOwners": { - "tag:client": ["odin@example.com"], - "tag:exit": ["odin@example.com"], - "tag:pidgey": ["odin@example.com"], - "tag:pidgeotto": ["odin@example.com"], - "tag:group-a": ["odin@example.com"], - "tag:group-b": ["odin@example.com"], - "tag:ha": ["odin@example.com"], - "tag:prod": ["odin@example.com"], - "tag:router": ["odin@example.com"], - "tag:spearow": ["odin@example.com"], - "tag:fearow": ["odin@example.com"], - "tag:server": ["odin@example.com"] - }} - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": ["10.33.0.0/16"] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": ["10.55.0.0/16"] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": ["10.44.0.0/16"] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": ["10.33.0.0/16"] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 742807651721642, - "StableID": "nMtKZFJRo611CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 742807651721642, - "Key": "nodekey:45d7df813905a0e39a63843408c508d051e40d88ff64a02f2c5d8246661eb716", - "DiscoKey": "discokey:3242b6abcbfdbe7f55ad497daa1ecc13a27a1503bbd14172abe6a15593071a41", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:49637", - "10.65.0.27:49637", - "172.17.0.1:49637", - "172.18.0.1:49637", - "172.19.0.1:49637", - "172.20.0.1:49637", - "172.21.0.1:49637", - "172.22.0.1:49637", - "172.23.0.1:49637", - "172.24.0.1:49637", - "172.25.0.1:49637", - "172.27.0.1:49637", - "172.28.0.1:49637", - "172.29.0.1:49637", - "172.30.0.1:49637" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:56.956275273Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:45d7df813905a0e39a63843408c508d051e40d88ff64a02f2c5d8246661eb716", - "MachineKey": "mkey:0de60b0f194ebaa9cb84a2a734a76526bee25283326a10c2e984c7908a7e267f", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"742807651721642": { - "ID": 742807651721642, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }}, "blastoise": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4115946452487668, - "StableID": "n5bNcAw79Z11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 4115946452487668, - "Key": "nodekey:7c578e256bcd180c9157581f28c9aae903b4b535cc4880f7276c956567e7644c", - "DiscoKey": "discokey:0dd3cc956f91bfbf57a0342898041ee443f28e01f57e0d825cc9a5b4f6f4c348", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:47019", - "10.65.0.27:47019", - "172.17.0.1:47019", - "172.18.0.1:47019", - "172.19.0.1:47019", - "172.20.0.1:47019", - "172.21.0.1:47019", - "172.22.0.1:47019", - "172.23.0.1:47019", - "172.24.0.1:47019", - "172.25.0.1:47019", - "172.27.0.1:47019", - "172.28.0.1:47019", - "172.29.0.1:47019", - "172.30.0.1:47019" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:53.752738091Z", - "Tags": ["tag:exit", "tag:router"], - "PrimaryRoutes": ["10.33.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:7c578e256bcd180c9157581f28c9aae903b4b535cc4880f7276c956567e7644c", - "MachineKey": "mkey:969d1cd089f03379b70814a95f0ef16ce2df65de187e76e5c474f6a822d5a369", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4115946452487668": { - "ID": 4115946452487668, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }} - }}, "bulbasaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 117941651091692, - "StableID": "nD9RqN8Rv111CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:179b877ca5f2801740968a2d5920c58c08b35447b912e7c8ab4ea986e85ff513", - "KeyExpiry": "2026-11-14T09:10:58Z", - "DiscoKey": "discokey:57fcdacb9de1e6cbfcc57b69cb0413e61f99817b4ac91517935c39d4d994495c", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:47480", - "10.65.0.27:47480", - "172.17.0.1:47480", - "172.18.0.1:47480", - "172.19.0.1:47480", - "172.20.0.1:47480", - "172.21.0.1:47480", - "172.22.0.1:47480", - "172.23.0.1:47480", - "172.24.0.1:47480", - "172.25.0.1:47480", - "172.27.0.1:47480", - "172.28.0.1:47480", - "172.29.0.1:47480", - "172.30.0.1:47480" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:58.549729838Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:179b877ca5f2801740968a2d5920c58c08b35447b912e7c8ab4ea986e85ff513", - "MachineKey": "mkey:87c7679a59c128c8d5e3ae69c84713fa14e7e2989cc9d8d43365a66fb14fa85e", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }} - }}, "charmander": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8611553749726689, - "StableID": "nAZ1z2vBFA21CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 8611553749726689, - "Key": "nodekey:5eb431f788096d6d4c2ad5b7639f2f76630b531981a275f6c7fb478462cadf6a", - "DiscoKey": "discokey:d941d6a21f41536a54f30f1b4360f1149dd8dad27c3826f6d16bd0c71a19e209", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:54587", - "10.65.0.27:54587", - "172.17.0.1:54587", - "172.18.0.1:54587", - "172.19.0.1:54587", - "172.20.0.1:54587", - "172.21.0.1:54587", - "172.22.0.1:54587", - "172.23.0.1:54587", - "172.24.0.1:54587", - "172.25.0.1:54587", - "172.27.0.1:54587", - "172.28.0.1:54587", - "172.29.0.1:54587", - "172.30.0.1:54587" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:52.167516116Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:5eb431f788096d6d4c2ad5b7639f2f76630b531981a275f6c7fb478462cadf6a", - "MachineKey": "mkey:3c4cee07a759e9892b8be7839e99dd3f8eda7c74f9e28b50672111fa6082aa45", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8611553749726689": { - "ID": 8611553749726689, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - }}, "fearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2962818332067504, - "StableID": "n36zJPDs8Q11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 2962818332067504, - "Key": "nodekey:ae65fa0cbc483038e5b31f1aa96ee6f9de15ce74a70fd63782c01efc14b7ed2a", - "DiscoKey": "discokey:f5f3dbeb346aa6043ae7f18e7fce55ba7675996f32cbc6b35ecfed9221ca686d", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128", "10.55.0.0/16"], - "Endpoints": [ - "77.164.248.136:48210", - "10.65.0.27:48210", - "172.17.0.1:48210", - "172.18.0.1:48210", - "172.19.0.1:48210", - "172.20.0.1:48210", - "172.21.0.1:48210", - "172.22.0.1:48210", - "172.23.0.1:48210", - "172.24.0.1:48210", - "172.25.0.1:48210", - "172.27.0.1:48210", - "172.28.0.1:48210", - "172.29.0.1:48210", - "172.30.0.1:48210" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:54.809749328Z", - "Tags": ["tag:fearow"], - "PrimaryRoutes": ["10.55.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ae65fa0cbc483038e5b31f1aa96ee6f9de15ce74a70fd63782c01efc14b7ed2a", - "MachineKey": "mkey:627d45ac3bc7cb556e179a75a00279e5c2c42190d49f940005ed9446c6e0d400", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"2962818332067504": { - "ID": 2962818332067504, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - }}, "ivysaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2348533302157090, - "StableID": "n5nkjGzeLK11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:4fdef654e4461936edc780b227c460829b3f690acdf333c856b5bbcb03346862", - "KeyExpiry": "2026-11-14T09:10:57Z", - "DiscoKey": "discokey:9790b60c664988dd878024181e01253bbaf80ca667a48f7d2227e3a3933f7e24", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54379", - "10.65.0.27:54379", - "172.17.0.1:54379", - "172.18.0.1:54379", - "172.19.0.1:54379", - "172.20.0.1:54379", - "172.21.0.1:54379", - "172.22.0.1:54379", - "172.23.0.1:54379", - "172.24.0.1:54379", - "172.25.0.1:54379", - "172.27.0.1:54379", - "172.28.0.1:54379", - "172.29.0.1:54379", - "172.30.0.1:54379" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:57.484455263Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4fdef654e4461936edc780b227c460829b3f690acdf333c856b5bbcb03346862", - "MachineKey": "mkey:bbf6b2f42e88d79b842a31699f972aaddd3822f7b782c3c5a5cb30303f42cb6c", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - }}, "kakuna": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8316404855452220, - "StableID": "nBpNtVrWw721CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 8316404855452220, - "Key": "nodekey:fc6c4f97df6155da8f12135b8e5b107fb3c11447a0c78aed4f97019a76355a09", - "DiscoKey": "discokey:7c5b44e12f1c86dd4320eec977184995f03f1f70bcd01add7270be947871fd11", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:52381", - "10.65.0.27:52381", - "172.17.0.1:52381", - "172.18.0.1:52381", - "172.19.0.1:52381", - "172.20.0.1:52381", - "172.21.0.1:52381", - "172.22.0.1:52381", - "172.23.0.1:52381", - "172.24.0.1:52381", - "172.25.0.1:52381", - "172.27.0.1:52381", - "172.28.0.1:52381", - "172.29.0.1:52381", - "172.30.0.1:52381" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:56.419139003Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:fc6c4f97df6155da8f12135b8e5b107fb3c11447a0c78aed4f97019a76355a09", - "MachineKey": "mkey:1f10dce91e6f48aa397e1f55ec84d7bbf700b6eae603a5cb6773d60786f6de62", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8316404855452220": { - "ID": 8316404855452220, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - }}, "pidgeotto": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 781141996409684, - "StableID": "nP81LzGn6711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 781141996409684, - "Key": "nodekey:8454a17c478cbd84c2bbbe569015aca27171259ecc0fec3c261715289f9bdf78", - "DiscoKey": "discokey:bd3827365b07af564765413e8aa789775efb48d5ea4bc8407a8ae32babc4a455", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:39405", - "10.65.0.27:39405", - "172.17.0.1:39405", - "172.18.0.1:39405", - "172.19.0.1:39405", - "172.20.0.1:39405", - "172.21.0.1:39405", - "172.22.0.1:39405", - "172.23.0.1:39405", - "172.24.0.1:39405", - "172.25.0.1:39405", - "172.27.0.1:39405", - "172.28.0.1:39405", - "172.29.0.1:39405", - "172.30.0.1:39405" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:51.639009594Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8454a17c478cbd84c2bbbe569015aca27171259ecc0fec3c261715289f9bdf78", - "MachineKey": "mkey:e33eca0d1703f9f889f42c5eb3b55295e214f0d43849ee08a2062989966fdd7a", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"781141996409684": { - "ID": 781141996409684, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - }}, "pidgey": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 7957670396407127, - "StableID": "nNamASW39521CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 7957670396407127, - "Key": "nodekey:0fdeaf58178609371f1a726273f49154455ce398311e26fc294e975bedccba05", - "DiscoKey": "discokey:bc4adfb590e9db28fcb7b36af00db93ce2472d66b94893c7656591521feb395d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:41325", - "10.65.0.27:41325", - "172.17.0.1:41325", - "172.18.0.1:41325", - "172.19.0.1:41325", - "172.20.0.1:41325", - "172.21.0.1:41325", - "172.22.0.1:41325", - "172.23.0.1:41325", - "172.24.0.1:41325", - "172.25.0.1:41325", - "172.27.0.1:41325", - "172.28.0.1:41325", - "172.29.0.1:41325", - "172.30.0.1:41325" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:51.102683657Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:0fdeaf58178609371f1a726273f49154455ce398311e26fc294e975bedccba05", - "MachineKey": "mkey:2c3d102d9cc29041131813cadb074cf83965fd8b052a09e1d2ff9c7886c18505", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"7957670396407127": { - "ID": 7957670396407127, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - }}, "raticate": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 8774142286825719, - "StableID": "ncXPFhqpWB21CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 8774142286825719, - "Key": "nodekey:4b5b22a10939393effca670f78d3c2ce7694db849fc0315b5b14155d5b4da051", - "DiscoKey": "discokey:c4b76557ff2311f5b95d2357d140b771ca79f18c8f522270e3203e7204f5fd4a", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:39952", - "10.65.0.27:39952", - "172.17.0.1:39952", - "172.18.0.1:39952", - "172.19.0.1:39952", - "172.20.0.1:39952", - "172.21.0.1:39952", - "172.22.0.1:39952", - "172.23.0.1:39952", - "172.24.0.1:39952", - "172.25.0.1:39952", - "172.27.0.1:39952", - "172.28.0.1:39952", - "172.29.0.1:39952", - "172.30.0.1:39952" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:53.21995137Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4b5b22a10939393effca670f78d3c2ce7694db849fc0315b5b14155d5b4da051", - "MachineKey": "mkey:dd498639926b5c582d320e8b36c1b76add012d763e00a74d441ceabc06d1b72c", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"8774142286825719": { - "ID": 8774142286825719, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - }}, "rattata": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 5603212911954104, - "StableID": "nVdEjJuhkk11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 5603212911954104, - "Key": "nodekey:308c261c7eb0104abf3e66f1646be742cdcf06036d059f919e76dfe88a2b6572", - "DiscoKey": "discokey:2cc68f99012b0d824d6fd24ace704e106e80712e77fcfba34054014f7a730e69", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:33694", - "10.65.0.27:33694", - "172.17.0.1:33694", - "172.18.0.1:33694", - "172.19.0.1:33694", - "172.20.0.1:33694", - "172.21.0.1:33694", - "172.22.0.1:33694", - "172.23.0.1:33694", - "172.24.0.1:33694", - "172.25.0.1:33694", - "172.27.0.1:33694", - "172.28.0.1:33694", - "172.29.0.1:33694", - "172.30.0.1:33694" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:52.700936185Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:308c261c7eb0104abf3e66f1646be742cdcf06036d059f919e76dfe88a2b6572", - "MachineKey": "mkey:5f5f1ff0ccb1742d1d8cb0316a09af2fdd3fe95c92fb9fe3226ce6321ee14371", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"5603212911954104": { - "ID": 5603212911954104, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }} - }}, "spearow": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 4263580853033713, - "StableID": "npe53H3zHa11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 4263580853033713, - "Key": "nodekey:ac5983ee49695f3ddbafe48311a097ecb9cb0e4806907c06323b6ac58c9af64f", - "DiscoKey": "discokey:29fa1a3518796d0f50fc5b5c0b36f41eb6ac3b92a6a3e0d959fc5d8df0082a2e", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128", "10.44.0.0/16"], - "Endpoints": [ - "77.164.248.136:55465", - "10.65.0.27:55465", - "172.17.0.1:55465", - "172.18.0.1:55465", - "172.19.0.1:55465", - "172.20.0.1:55465", - "172.21.0.1:55465", - "172.22.0.1:55465", - "172.23.0.1:55465", - "172.24.0.1:55465", - "172.25.0.1:55465", - "172.27.0.1:55465", - "172.28.0.1:55465", - "172.29.0.1:55465", - "172.30.0.1:55465" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:54.283207375Z", - "Tags": ["tag:spearow"], - "PrimaryRoutes": ["10.44.0.0/16"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ac5983ee49695f3ddbafe48311a097ecb9cb0e4806907c06323b6ac58c9af64f", - "MachineKey": "mkey:0ad13c072d304401be41eb0d7000eaf9adfc111ec3ce62bf4e6ab9f7b36f7745", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"4263580853033713": { - "ID": 4263580853033713, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - }}, "squirtle": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 2715924301317889, - "StableID": "nz5BG7j3DN11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 2715924301317889, - "Key": "nodekey:0ce04552f96890506449aad321dc9453835716697eb0d0bdcc0782613036994c", - "DiscoKey": "discokey:812ba325824c522e05582e005744f3f710b87d77710505678c210dc610b90e4e", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128", "10.33.0.0/16"], - "Endpoints": [ - "77.164.248.136:49272", - "10.65.0.27:49272", - "172.17.0.1:49272", - "172.18.0.1:49272", - "172.19.0.1:49272", - "172.20.0.1:49272", - "172.21.0.1:49272", - "172.22.0.1:49272", - "172.23.0.1:49272", - "172.24.0.1:49272", - "172.25.0.1:49272", - "172.27.0.1:49272", - "172.28.0.1:49272", - "172.29.0.1:49272", - "172.30.0.1:49272" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:10:55.348461932Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:0ce04552f96890506449aad321dc9453835716697eb0d0bdcc0782613036994c", - "MachineKey": "mkey:7746b6bcfc673aedac1e7d4cdbec81cf08347bcecdb45258daac5d32109ac037", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"2715924301317889": { - "ID": 2715924301317889, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - }}, "venusaur": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 7292111442929489, - "StableID": "nGQrYLQcwy11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:d2a96f1163c963756c7fa4b41a4269ec0abe4e61cb3971e0a4845af4f360b141", - "KeyExpiry": "2026-11-14T09:10:58Z", - "DiscoKey": "discokey:57f04a4cd9d35bc8c9131a544af90524da77c3a07573ac011e70856704bad928", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:48636", - "10.65.0.27:48636", - "172.17.0.1:48636", - "172.18.0.1:48636", - "172.19.0.1:48636", - "172.20.0.1:48636", - "172.21.0.1:48636", - "172.22.0.1:48636", - "172.23.0.1:48636", - "172.24.0.1:48636", - "172.25.0.1:48636", - "172.27.0.1:48636", - "172.28.0.1:48636", - "172.29.0.1:48636", - "172.30.0.1:48636" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:58.013696855Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d2a96f1163c963756c7fa4b41a4269ec0abe4e61cb3971e0a4845af4f360b141", - "MachineKey": "mkey:00ab69d50c24300b46e0936b08cd28504b8ee7060f042fb4753e24bd0cb00b65", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }} - }}, "weedle": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 7716405826229389, - "StableID": "ng8sGztmF321CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 7716405826229389, - "Key": "nodekey:d8b35c1dface4d18b9fafa93c6b303f4b773febf51f4c234c862f18a5de8250b", - "DiscoKey": "discokey:4bba182fd9a1812cf3bfef9edfdd7933bbdafb956a2b1863555b289f1d2e6977", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:54114", - "10.65.0.27:54114", - "172.17.0.1:54114", - "172.18.0.1:54114", - "172.19.0.1:54114", - "172.20.0.1:54114", - "172.21.0.1:54114", - "172.22.0.1:54114", - "172.23.0.1:54114", - "172.24.0.1:54114", - "172.25.0.1:54114", - "172.27.0.1:54114", - "172.28.0.1:54114", - "172.29.0.1:54114", - "172.30.0.1:54114" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:10:55.882122812Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d8b35c1dface4d18b9fafa93c6b303f4b773febf51f4c234c862f18a5de8250b", - "MachineKey": "mkey:62d8c49eed6420a2f1ae67fe4886e9b678974846b37d689d07fce88e42563848", - "Peers": [], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"7716405826229389": { - "ID": 7716405826229389, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - }}} -} diff --git a/hscontrol/policy/v2/testdata/grant_results/via-grant-v51.hujson b/hscontrol/policy/v2/testdata/grant_results/via-grant-v51.hujson deleted file mode 100644 index d5105e8e7..000000000 --- a/hscontrol/policy/v2/testdata/grant_results/via-grant-v51.hujson +++ /dev/null @@ -1,2157 +0,0 @@ -// via-grant-v51 -// -// via grant v51 ipv6 4via6 dst host alias /64 broader than advertised /120 -// -// Nodes with filter rules: 1 of 2 -// Captured at: 2026-05-18T09:11:44Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "via-grant-v51", - "description": "via grant v51 ipv6 4via6 dst host alias /64 broader than advertised /120", - "category": "grant", - "captured_at": "2026-05-18T09:11:44.189311885Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n \n \n \n \n \n \n \n \n{\n\t\"category\": \"grant\",\n\t\"description\": \"via grant v51 ipv6 4via6 dst host alias /64 broader than advertised /120\",\n\t\"id\": \"via-grant-v51\",\n\t\"options\": {\"capture_whois\": true},\n\t\"policy\": {\"autoApprovers\": {\"routes\": {\n\t\t\"fd7a:115c:a1e0:b1a:0:13:ad2:7300/120\": [\"tag:router\"]\n\t}}, \"grants\": [{\n\t\t\"dst\": [\"example-4via6\"],\n\t\t\"ip\": [\"icmp:*\", \"tcp:3389\"],\n\t\t\"src\": [\"tag:group-a\"],\n\t\t\"via\": [\"tag:router\"]\n\t}], \"hosts\": {\n\t\t\"example-4via6\": \"fd7a:115c:a1e0:b1a::/64\"\n\t}, \"tagOwners\": {\n\t\t\"tag:group-a\": [\"odin@example.com\"],\n\t\t\"tag:router\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": {\n\t\t\"users\": [{\"id\": 1, \"name\": \"odin\", \"email\": \"odin@example.com\"}],\n\t\t\"nodes\": {\n\t\t\t\"client-a\": {\n\t\t\t\t\"hostname\": \"client-a\",\n\t\t\t\t\"tags\": [\"tag:group-a\"],\n\t\t\t\t\"routable_ips\": [],\n\t\t\t\t\"approved_routes\": []\n\t\t\t},\n\t\t\t\"router-6via\": {\n\t\t\t\t\"hostname\": \"router-6via\",\n\t\t\t\t\"tags\": [\"tag:router\"],\n\t\t\t\t\"routable_ips\": [\"fd7a:115c:a1e0:b1a:0:13:ad2:7300/120\"],\n\t\t\t\t\"approved_routes\": []\n\t\t\t}\n\t\t}\n\t}\n}\n", - "scenario_path": "scenarios/grant/via-grant-v51.hujson", - "full_policy": { - "autoApprovers": {"routes": {"fd7a:115c:a1e0:b1a:0:13:ad2:7300/120": ["tag:router"]}}, - "grants": [{ - "dst": ["example-4via6"], - "ip": ["icmp:*", "tcp:3389"], - "src": ["tag:group-a"], - "via": ["tag:router"] - }], - "hosts": {"example-4via6": "fd7a:115c:a1e0:b1a::/64"}, - "tagOwners": {"tag:group-a": ["odin@example.com"], "tag:router": ["odin@example.com"]} - } - }, - "topology": { - "users": [{"id": 1, "name": "odin", "email": "odin@example.com"}], - "nodes": {"client-a": { - "hostname": "client-a", - "tags": ["tag:group-a"], - "ipv4": "100.95.136.121", - "ipv6": "fd7a:115c:a1e0::bd37:8879", - "routable_ips": [], - "approved_routes": [] - }, "router-6via": { - "hostname": "router-6via", - "tags": ["tag:router"], - "ipv4": "100.120.10.2", - "ipv6": "fd7a:115c:a1e0::7537:a02", - "routable_ips": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "approved_routes": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"] - }} - }, - "captures": {"client-a": {"netmap": { - "Cached": false, - "SelfNode": { - "ID": 1523520198144354, - "StableID": "nXhtrwH1uC11CNTRL", - "Name": "client-a.tail78f774.ts.net.", - "User": 1523520198144354, - "Key": "nodekey:e7e273fc7d70ffb8cef6797a3e1725f5d1f25c64a2ca47be203862f1bd5f8878", - "DiscoKey": "discokey:42f94cc27341c00d3e3e056d1840f1db99a5fc47d5b2728e8739a8d6c6970365", - "Addresses": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "AllowedIPs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "Endpoints": [ - "77.164.248.136:44313", - "10.65.0.27:44313", - "172.17.0.1:44313", - "172.18.0.1:44313", - "172.19.0.1:44313", - "172.20.0.1:44313", - "172.21.0.1:44313", - "172.22.0.1:44313", - "172.23.0.1:44313", - "172.24.0.1:44313", - "172.25.0.1:44313", - "172.27.0.1:44313", - "172.28.0.1:44313", - "172.29.0.1:44313", - "172.30.0.1:44313" - ], - "Hostinfo": {"Hostname": "client-a", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 60228}, - {"Proto": "peerapi6", "Port": 60228}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-18T09:11:46.4498255Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "client-a", - "ComputedNameWithHost": "client-a" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e7e273fc7d70ffb8cef6797a3e1725f5d1f25c64a2ca47be203862f1bd5f8878", - "MachineKey": "mkey:a9045bad4803f49067aec4ecf0156cb2f030a26466c9dd03b6a9da3b003e7648", - "Peers": [{ - "ID": 3557161560388091, - "StableID": "nAgWAwb3nU11CNTRL", - "Name": "router-6via.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:acaabecf14a43b9a74d89c9fe3f9e6ba3421de3c28570d015dc1a11e75b80a01", - "DiscoKey": "discokey:ab37a1bb878d7322f28d8ac80759bc4347819f45d58279515a04fec9fe581672", - "Addresses": ["100.120.10.2/32", "fd7a:115c:a1e0::7537:a02/128"], - "AllowedIPs": [ - "100.120.10.2/32", - "fd7a:115c:a1e0::7537:a02/128", - "fd7a:115c:a1e0:b1a:0:13:ad2:7300/120" - ], - "Endpoints": [ - "77.164.248.136:41317", - "10.65.0.27:41317", - "172.17.0.1:41317", - "172.18.0.1:41317", - "172.19.0.1:41317", - "172.20.0.1:41317", - "172.21.0.1:41317", - "172.22.0.1:41317", - "172.23.0.1:41317", - "172.24.0.1:41317", - "172.25.0.1:41317", - "172.27.0.1:41317", - "172.28.0.1:41317", - "172.29.0.1:41317", - "172.30.0.1:41317" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "router-6via", "Services": [ - {"Proto": "peerapi4", "Port": 59740}, - {"Proto": "peerapi6", "Port": 59740} - ]}, - "Created": "2026-05-18T09:11:46.979865427Z", - "Cap": 131, - "Tags": ["tag:router"], - "PrimaryRoutes": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "Online": true, - "ComputedName": "router-6via", - "ComputedNameWithHost": "router-6via" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [], - "PacketFilterRules": null, - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1523520198144354": { - "ID": 1523520198144354, - "LoginName": "client-a.tail78f774.ts.net", - "DisplayName": "client-a" - }} - }, "whois": {"100.120.10.2": {"Node": { - "ID": 3557161560388091, - "StableID": "nAgWAwb3nU11CNTRL", - "Name": "router-6via.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:acaabecf14a43b9a74d89c9fe3f9e6ba3421de3c28570d015dc1a11e75b80a01", - "DiscoKey": "discokey:ab37a1bb878d7322f28d8ac80759bc4347819f45d58279515a04fec9fe581672", - "Addresses": ["100.120.10.2/32", "fd7a:115c:a1e0::7537:a02/128"], - "AllowedIPs": [ - "100.120.10.2/32", - "fd7a:115c:a1e0::7537:a02/128", - "fd7a:115c:a1e0:b1a:0:13:ad2:7300/120" - ], - "Endpoints": [ - "77.164.248.136:41317", - "10.65.0.27:41317", - "172.17.0.1:41317", - "172.18.0.1:41317", - "172.19.0.1:41317", - "172.20.0.1:41317", - "172.21.0.1:41317", - "172.22.0.1:41317", - "172.23.0.1:41317", - "172.24.0.1:41317", - "172.25.0.1:41317", - "172.27.0.1:41317", - "172.28.0.1:41317", - "172.29.0.1:41317", - "172.30.0.1:41317" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "router-6via", "Services": [ - {"Proto": "peerapi4", "Port": 59740}, - {"Proto": "peerapi6", "Port": 59740} - ]}, - "Created": "2026-05-18T09:11:46.979865427Z", - "Cap": 131, - "Tags": ["tag:router"], - "PrimaryRoutes": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "Online": true, - "ComputedName": "router-6via", - "ComputedNameWithHost": "router-6via" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::7537:a02": {"Node": { - "ID": 3557161560388091, - "StableID": "nAgWAwb3nU11CNTRL", - "Name": "router-6via.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:acaabecf14a43b9a74d89c9fe3f9e6ba3421de3c28570d015dc1a11e75b80a01", - "DiscoKey": "discokey:ab37a1bb878d7322f28d8ac80759bc4347819f45d58279515a04fec9fe581672", - "Addresses": ["100.120.10.2/32", "fd7a:115c:a1e0::7537:a02/128"], - "AllowedIPs": [ - "100.120.10.2/32", - "fd7a:115c:a1e0::7537:a02/128", - "fd7a:115c:a1e0:b1a:0:13:ad2:7300/120" - ], - "Endpoints": [ - "77.164.248.136:41317", - "10.65.0.27:41317", - "172.17.0.1:41317", - "172.18.0.1:41317", - "172.19.0.1:41317", - "172.20.0.1:41317", - "172.21.0.1:41317", - "172.22.0.1:41317", - "172.23.0.1:41317", - "172.24.0.1:41317", - "172.25.0.1:41317", - "172.27.0.1:41317", - "172.28.0.1:41317", - "172.29.0.1:41317", - "172.30.0.1:41317" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "router-6via", "Services": [ - {"Proto": "peerapi4", "Port": 59740}, - {"Proto": "peerapi6", "Port": 59740} - ]}, - "Created": "2026-05-18T09:11:46.979865427Z", - "Cap": 131, - "Tags": ["tag:router"], - "PrimaryRoutes": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "Online": true, - "ComputedName": "router-6via", - "ComputedNameWithHost": "router-6via" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}, "router-6via": {"packet_filter_rules": [{ - "SrcIPs": ["100.95.136.121", "fd7a:115c:a1e0::bd37:8879"], - "DstPorts": [{"IP": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 0, "Last": 65535}}], - "IPProto": [1] - }, {"SrcIPs": ["100.95.136.121", "fd7a:115c:a1e0::bd37:8879"], "DstPorts": [ - {"IP": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 3389, "Last": 3389}} - ], "IPProto": [6]}], "packet_filter_matches": [{ - "IPProto": [1], - "Srcs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "SrcCaps": null, - "Dsts": [{"Net": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }, { - "IPProto": [6], - "Srcs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "SrcCaps": null, - "Dsts": [ - {"Net": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 3389, "Last": 3389}} - ], - "Caps": [] - }], "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3557161560388091, - "StableID": "nAgWAwb3nU11CNTRL", - "Name": "router-6via.tail78f774.ts.net.", - "User": 3557161560388091, - "Key": "nodekey:acaabecf14a43b9a74d89c9fe3f9e6ba3421de3c28570d015dc1a11e75b80a01", - "DiscoKey": "discokey:ab37a1bb878d7322f28d8ac80759bc4347819f45d58279515a04fec9fe581672", - "Addresses": ["100.120.10.2/32", "fd7a:115c:a1e0::7537:a02/128"], - "AllowedIPs": [ - "100.120.10.2/32", - "fd7a:115c:a1e0::7537:a02/128", - "fd7a:115c:a1e0:b1a:0:13:ad2:7300/120" - ], - "Endpoints": [ - "77.164.248.136:41317", - "10.65.0.27:41317", - "172.17.0.1:41317", - "172.18.0.1:41317", - "172.19.0.1:41317", - "172.20.0.1:41317", - "172.21.0.1:41317", - "172.22.0.1:41317", - "172.23.0.1:41317", - "172.24.0.1:41317", - "172.25.0.1:41317", - "172.27.0.1:41317", - "172.28.0.1:41317", - "172.29.0.1:41317", - "172.30.0.1:41317" - ], - "Hostinfo": { - "Hostname": "router-6via", - "RoutableIPs": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 59740}, - {"Proto": "peerapi6", "Port": 59740}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-18T09:11:46.979865427Z", - "Tags": ["tag:router"], - "PrimaryRoutes": ["fd7a:115c:a1e0:b1a:0:13:ad2:7300/120"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "router-6via", - "ComputedNameWithHost": "router-6via" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:acaabecf14a43b9a74d89c9fe3f9e6ba3421de3c28570d015dc1a11e75b80a01", - "MachineKey": "mkey:504ebafa5399ca52470362f74b2b24da583b53bf1e20e5cdf0bc3bf0f9c84c6a", - "Peers": [{ - "ID": 1523520198144354, - "StableID": "nXhtrwH1uC11CNTRL", - "Name": "client-a.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e7e273fc7d70ffb8cef6797a3e1725f5d1f25c64a2ca47be203862f1bd5f8878", - "DiscoKey": "discokey:42f94cc27341c00d3e3e056d1840f1db99a5fc47d5b2728e8739a8d6c6970365", - "Addresses": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "AllowedIPs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "Endpoints": [ - "77.164.248.136:44313", - "10.65.0.27:44313", - "172.17.0.1:44313", - "172.18.0.1:44313", - "172.19.0.1:44313", - "172.20.0.1:44313", - "172.21.0.1:44313", - "172.22.0.1:44313", - "172.23.0.1:44313", - "172.24.0.1:44313", - "172.25.0.1:44313", - "172.27.0.1:44313", - "172.28.0.1:44313", - "172.29.0.1:44313", - "172.30.0.1:44313" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "client-a", "Services": [ - {"Proto": "peerapi4", "Port": 60228}, - {"Proto": "peerapi6", "Port": 60228} - ]}, - "Created": "2026-05-18T09:11:46.4498255Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "client-a", - "ComputedNameWithHost": "client-a" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{ - "IPProto": [1], - "Srcs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "SrcCaps": null, - "Dsts": [{"Net": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 0, "Last": 65535}}], - "Caps": [] - }, { - "IPProto": [6], - "Srcs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "SrcCaps": null, - "Dsts": [ - {"Net": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 3389, "Last": 3389}} - ], - "Caps": [] - }], - "PacketFilterRules": [{ - "SrcIPs": ["100.95.136.121", "fd7a:115c:a1e0::bd37:8879"], - "DstPorts": [{"IP": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 0, "Last": 65535}}], - "IPProto": [1] - }, {"SrcIPs": ["100.95.136.121", "fd7a:115c:a1e0::bd37:8879"], "DstPorts": [ - {"IP": "fd7a:115c:a1e0:b1a::/64", "Ports": {"First": 3389, "Last": 3389}} - ], "IPProto": [6]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3557161560388091": { - "ID": 3557161560388091, - "LoginName": "router-6via.tail78f774.ts.net", - "DisplayName": "router-6via" - }} - }, "whois": {"100.95.136.121": {"Node": { - "ID": 1523520198144354, - "StableID": "nXhtrwH1uC11CNTRL", - "Name": "client-a.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e7e273fc7d70ffb8cef6797a3e1725f5d1f25c64a2ca47be203862f1bd5f8878", - "DiscoKey": "discokey:42f94cc27341c00d3e3e056d1840f1db99a5fc47d5b2728e8739a8d6c6970365", - "Addresses": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "AllowedIPs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "Endpoints": [ - "77.164.248.136:44313", - "10.65.0.27:44313", - "172.17.0.1:44313", - "172.18.0.1:44313", - "172.19.0.1:44313", - "172.20.0.1:44313", - "172.21.0.1:44313", - "172.22.0.1:44313", - "172.23.0.1:44313", - "172.24.0.1:44313", - "172.25.0.1:44313", - "172.27.0.1:44313", - "172.28.0.1:44313", - "172.29.0.1:44313", - "172.30.0.1:44313" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "client-a", "Services": [ - {"Proto": "peerapi4", "Port": 60228}, - {"Proto": "peerapi6", "Port": 60228} - ]}, - "Created": "2026-05-18T09:11:46.4498255Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "client-a", - "ComputedNameWithHost": "client-a" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}, "fd7a:115c:a1e0::bd37:8879": {"Node": { - "ID": 1523520198144354, - "StableID": "nXhtrwH1uC11CNTRL", - "Name": "client-a.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e7e273fc7d70ffb8cef6797a3e1725f5d1f25c64a2ca47be203862f1bd5f8878", - "DiscoKey": "discokey:42f94cc27341c00d3e3e056d1840f1db99a5fc47d5b2728e8739a8d6c6970365", - "Addresses": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "AllowedIPs": ["100.95.136.121/32", "fd7a:115c:a1e0::bd37:8879/128"], - "Endpoints": [ - "77.164.248.136:44313", - "10.65.0.27:44313", - "172.17.0.1:44313", - "172.18.0.1:44313", - "172.19.0.1:44313", - "172.20.0.1:44313", - "172.21.0.1:44313", - "172.22.0.1:44313", - "172.23.0.1:44313", - "172.24.0.1:44313", - "172.25.0.1:44313", - "172.27.0.1:44313", - "172.28.0.1:44313", - "172.29.0.1:44313", - "172.30.0.1:44313" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "client-a", "Services": [ - {"Proto": "peerapi4", "Port": 60228}, - {"Proto": "peerapi6", "Port": 60228} - ]}, - "Created": "2026-05-18T09:11:46.4498255Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "client-a", - "ComputedNameWithHost": "client-a" - }, "UserProfile": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "CapMap": null}}}} -} diff --git a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-accept-and-deny-same-user.hujson b/hscontrol/policy/v2/testdata/sshtest_results/sshtest-accept-and-deny-same-user.hujson deleted file mode 100644 index 97354be01..000000000 --- a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-accept-and-deny-same-user.hujson +++ /dev/null @@ -1,21887 +0,0 @@ -// sshtest-accept-and-deny-same-user -// -// sshTests accept and deny same user -// -// Nodes with filter rules: 15 of 15 -// Captured at: 2026-05-13T09:29:30Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "sshtest-accept-and-deny-same-user", - "description": "sshTests accept and deny same user", - "category": "sshtest", - "captured_at": "2026-05-13T09:29:30.484173416Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "error": true, - "input": { - "api_response_code": 400, - "api_response_body": {"message": "test(s) failed"}, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n{\n\t\"category\": \"sshtest\",\n\t\"description\": \"sshTests accept and deny same user\",\n\t\"id\": \"sshtest-accept-and-deny-same-user\",\n\t\"options\": {\"expect_api_error\": true},\n\t\"policy\": {\"ssh\": [{\n\t\t\"action\": \"accept\",\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": [\"thor@example.org\"],\n\t\t\"users\": [\"root\"]\n\t}], \"sshTests\": [{\n\t\t\"accept\": [\"root\"],\n\t\t\"deny\": [\"root\"],\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": \"thor@example.org\"\n\t}], \"tagOwners\": {\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/ssh-edges/sshtest-accept-and-deny-same-user.hujson", - "full_policy": { - "ssh": [{ - "action": "accept", - "dst": ["tag:server"], - "src": ["thor@example.org"], - "users": ["root"] - }], - "sshTests": [{ - "accept": ["root"], - "deny": ["root"], - "dst": ["tag:server"], - "src": "thor@example.org" - }], - "tagOwners": {"tag:prod": ["odin@example.com"], "tag:server": ["odin@example.com"]} - } - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": [] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": [] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": [] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 2490480947865966, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "MachineKey": "mkey:e789755c833c6a7a59e1eeeebe5427f1c61ca60a05a26b7c5aff2c148e045860", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2490480947865966": { - "ID": 2490480947865966, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "blastoise": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1431992482076218, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:35.901475059Z", - "Tags": ["tag:exit", "tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "MachineKey": "mkey:ccc0bbc48f9fc9c5ee19f7bfeefdff39274dab9b426f1bb2f9d3a16fde72e32b", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1431992482076218": { - "ID": 1431992482076218, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "bulbasaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "MachineKey": "mkey:41b9f335ce3dcd09e212283f0ef5c79460f9d7187a965d137d9cc36191dd4434", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "charmander": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 4937306133179357, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:34.31032901Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "MachineKey": "mkey:a690f5c3222b7c555232db4514008d74e5680f01b190141d5ca2b610bb7a1f3e", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "4937306133179357": { - "ID": 4937306133179357, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - } - }, "fearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 5481495937921774, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:36.978336642Z", - "Tags": ["tag:fearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "MachineKey": "mkey:d4e4cef8367d4179df1f3f6eab01f0c027a4af4676aa02f8cb000d691dbf4663", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5481495937921774": { - "ID": 5481495937921774, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - } - }, "ivysaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "MachineKey": "mkey:073951eaa0cce8fff7ce4baddc2761675b5dad8e4aad59c7759aabc4f6e00738", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "kakuna": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 8028973883170165, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "MachineKey": "mkey:bf1fec9e3f2468c824b136d52213782f032be319a754215216e8a3b29e1f1230", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "8028973883170165": { - "ID": 8028973883170165, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - } - }, "pidgeotto": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 6209384882856680, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:33.764228462Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "MachineKey": "mkey:d42d20dcf211a7991b14926851e4a2b602d88e78adbaf6d8429f4cdb6a127f37", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "6209384882856680": { - "ID": 6209384882856680, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - } - }, "pidgey": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 2859212664711574, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:33.270318864Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "MachineKey": "mkey:fde774de496b45f37b87433f85b1a5b8c7230580071846d9782da312b62f333d", - "Peers": [{ - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2859212664711574": { - "ID": 2859212664711574, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "raticate": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 188686204918932, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "MachineKey": "mkey:5cda7b69260f3746f07290b2af2bd64d3d3d4eb73ae4a66ebe96f9a16f00f35a", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "188686204918932": { - "ID": 188686204918932, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "rattata": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 493614118971451, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "MachineKey": "mkey:c2313e64fc87f0087095531735c008cc6b6770b953c049b19923e68e7359b609", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "493614118971451": { - "ID": 493614118971451, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }} - } - }, "spearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 2215287914480306, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:36.429659202Z", - "Tags": ["tag:spearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "MachineKey": "mkey:03c14990d7385d3f338c92a84b8c28ea2ad118b13428944e47ea332e7ca1470f", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2215287914480306": { - "ID": 2215287914480306, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "squirtle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 5181403855005874, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:29:37.52519162Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "MachineKey": "mkey:c5c986715d5e2dabc33583f6c8177b695455f0680097ce0a717b2c7c6b59f268", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5181403855005874": { - "ID": 5181403855005874, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - } - }, "venusaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "MachineKey": "mkey:109dddfe3baf86bc889d781da383735af5d286dffa18d660f151ea6eddfff64f", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "weedle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5947777451816847, - "StableID": "nUXBDd2mSo11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 5947777451816847, - "Key": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "DiscoKey": "discokey:821b02cb0acf6fc1a36d2c95273c0663e0233c9d928fe1bdb60b6d57fa2c6f22", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:50083", - "10.65.0.27:50083", - "172.17.0.1:50083", - "172.18.0.1:50083", - "172.19.0.1:50083", - "172.20.0.1:50083", - "172.21.0.1:50083", - "172.22.0.1:50083", - "172.23.0.1:50083", - "172.24.0.1:50083", - "172.25.0.1:50083", - "172.26.0.1:50083", - "172.27.0.1:50083" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:29:38.144998256Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:9320fbe912baef9f83844e8f5526b56748d328d5ad9bcf9d4f76a3e855cb2147", - "MachineKey": "mkey:bdf859b2a1c0a38e4a22c649769e058a62ab6b18a421cb3b1914c18cc352a37b", - "Peers": [{ - "ID": 2859212664711574, - "StableID": "n3RUGmfwKP11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:257f18e69b65ec0c700b7e2184bca19a3cf77826021794928f6980d4ef38b248", - "DiscoKey": "discokey:44fd7f4dd7476297d1221f7d99732c75a33da40d06f1c636d0c4f27698923b71", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:43150", - "10.65.0.27:43150", - "172.17.0.1:43150", - "172.18.0.1:43150", - "172.19.0.1:43150", - "172.20.0.1:43150", - "172.21.0.1:43150", - "172.22.0.1:43150", - "172.23.0.1:43150", - "172.24.0.1:43150", - "172.25.0.1:43150", - "172.26.0.1:43150", - "172.27.0.1:43150" - ], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:29:33.270318864Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 6209384882856680, - "StableID": "n7eP4g1FVq11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f9506ef72035485fc2d27b1036bba84c599ee143c5e5db39f83b16e1c7c3d204", - "DiscoKey": "discokey:56bfd065fbf9aded449df431842d4e6b4e7d44457ad503c42380ad737c3c3669", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:44553", - "10.65.0.27:44553", - "172.17.0.1:44553", - "172.18.0.1:44553", - "172.19.0.1:44553", - "172.20.0.1:44553", - "172.21.0.1:44553", - "172.22.0.1:44553", - "172.23.0.1:44553", - "172.24.0.1:44553", - "172.25.0.1:44553", - "172.26.0.1:44553", - "172.27.0.1:44553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:29:33.764228462Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 4937306133179357, - "StableID": "nCgkyGf7Zf11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ccfd85a67c2ae7d59bd0dc941483f6764942db4d3e3473d6e3ea72dd8f4db405", - "DiscoKey": "discokey:9e488e31b507cc05ebb6ac225a6e756e8de09f01ac7e1425f097e7eee2c0970d", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53179", - "10.65.0.27:53179", - "172.17.0.1:53179", - "172.18.0.1:53179", - "172.19.0.1:53179", - "172.20.0.1:53179", - "172.21.0.1:53179", - "172.22.0.1:53179", - "172.23.0.1:53179", - "172.24.0.1:53179", - "172.25.0.1:53179", - "172.26.0.1:53179", - "172.27.0.1:53179" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:29:34.31032901Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 493614118971451, - "StableID": "nUZhPYQZr411CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:d1f09659d9ff3cde7edaf8075269b59c8bac1f27bc88b3fbc0a27fadbca2e54b", - "DiscoKey": "discokey:260fcd6a36050179da5c80d9561e7fc2361abfa0031182fc42a9975284ef484e", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:60113", - "10.65.0.27:60113", - "172.17.0.1:60113", - "172.18.0.1:60113", - "172.19.0.1:60113", - "172.20.0.1:60113", - "172.21.0.1:60113", - "172.22.0.1:60113", - "172.23.0.1:60113", - "172.24.0.1:60113", - "172.25.0.1:60113", - "172.26.0.1:60113", - "172.27.0.1:60113" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:29:34.841487957Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 188686204918932, - "StableID": "nmjao4UTU211CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:90929a1c49724dcf491f4acc8a70e70f4917540d41fefb552b2781a75cad4643", - "DiscoKey": "discokey:916e878a92a884cf8fbbc9bcb52a4206be177185311ad7bff9f62f5218030632", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:50198", - "10.65.0.27:50198", - "172.17.0.1:50198", - "172.18.0.1:50198", - "172.19.0.1:50198", - "172.20.0.1:50198", - "172.21.0.1:50198", - "172.22.0.1:50198", - "172.23.0.1:50198", - "172.24.0.1:50198", - "172.25.0.1:50198", - "172.26.0.1:50198", - "172.27.0.1:50198" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:29:35.35868711Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1431992482076218, - "StableID": "nFPm1q1ZBC11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:599a1b94de977777f3d8c3a8e0b472a2664b1edeb03e8da4bcd89c8c6b23a825", - "DiscoKey": "discokey:ce753b107bd5f488e3b6a7325ffd0442f0debe5213132977e2f95c8bd763884b", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:47841", - "10.65.0.27:47841", - "172.17.0.1:47841", - "172.18.0.1:47841", - "172.19.0.1:47841", - "172.20.0.1:47841", - "172.21.0.1:47841", - "172.22.0.1:47841", - "172.23.0.1:47841", - "172.24.0.1:47841", - "172.25.0.1:47841", - "172.26.0.1:47841", - "172.27.0.1:47841" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:29:35.901475059Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2215287914480306, - "StableID": "nh487irJJJ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4b064330c9f805ad51a4b3336b5d3687be97805d2c12670d472d6fdb9e000015", - "DiscoKey": "discokey:d569f11adb20bdbd3997acdebf4a11d7aab3609cd661e9f4e1862f858a9d2230", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:50524", - "10.65.0.27:50524", - "172.17.0.1:50524", - "172.18.0.1:50524", - "172.19.0.1:50524", - "172.20.0.1:50524", - "172.21.0.1:50524", - "172.22.0.1:50524", - "172.23.0.1:50524", - "172.24.0.1:50524", - "172.25.0.1:50524", - "172.26.0.1:50524", - "172.27.0.1:50524" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:29:36.429659202Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5481495937921774, - "StableID": "nhWvXzbaoj11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:645ef6e18e733de4391cc50812baa95407a0cdba46a222b12872e3c92b64b447", - "DiscoKey": "discokey:eade7d7c765e230e0a081b4dca22080209816989c9309c0ca23ba874a7382b0c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36768", - "10.65.0.27:36768", - "172.17.0.1:36768", - "172.18.0.1:36768", - "172.19.0.1:36768", - "172.20.0.1:36768", - "172.21.0.1:36768", - "172.22.0.1:36768", - "172.23.0.1:36768", - "172.24.0.1:36768", - "172.25.0.1:36768", - "172.26.0.1:36768", - "172.27.0.1:36768" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:29:36.978336642Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5181403855005874, - "StableID": "nDnQnChfTh11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:7af9bedfaf13444d28e1664137504d38acd3737d8df9eeac69018992665b601c", - "DiscoKey": "discokey:717cb5a3f65595c24e1a225bd4211fdd0022216d9e6268eef2344181e8b0cf72", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:36746", - "10.65.0.27:36746", - "172.17.0.1:36746", - "172.18.0.1:36746", - "172.19.0.1:36746", - "172.20.0.1:36746", - "172.21.0.1:36746", - "172.22.0.1:36746", - "172.23.0.1:36746", - "172.24.0.1:36746", - "172.25.0.1:36746", - "172.26.0.1:36746", - "172.27.0.1:36746" - ], - "HomeDERP": 4, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:29:37.52519162Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 8028973883170165, - "StableID": "n44L8hXLh521CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:3e1a94c6101554334d973f8d28097a3b1c352efc44b034ba9a3f710c9a378d00", - "DiscoKey": "discokey:80529a51f5492a8b47068444185c7f283fb9f84399ce6b3b5c5d218785df054b", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:35325", - "10.65.0.27:35325", - "172.17.0.1:35325", - "172.18.0.1:35325", - "172.19.0.1:35325", - "172.20.0.1:35325", - "172.21.0.1:35325", - "172.22.0.1:35325", - "172.23.0.1:35325", - "172.24.0.1:35325", - "172.25.0.1:35325", - "172.26.0.1:35325", - "172.27.0.1:35325" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:29:38.639659064Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 2490480947865966, - "StableID": "nFKU7GiwSL11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb405c595cd71a6fd381cd46bec02addb0728794748e0f330f2b75861235d600", - "DiscoKey": "discokey:7a3d09ff217e504c0b86360bd0d1aab6ba2719a28217d8a6850330843e296165", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:37486", - "10.65.0.27:37486", - "172.17.0.1:37486", - "172.18.0.1:37486", - "172.19.0.1:37486", - "172.20.0.1:37486", - "172.21.0.1:37486", - "172.22.0.1:37486", - "172.23.0.1:37486", - "172.24.0.1:37486", - "172.25.0.1:37486", - "172.26.0.1:37486", - "172.27.0.1:37486" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:29:39.168681166Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1589114381480400, - "StableID": "nbU3EjLiQD11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:1f3efd3a35eeb9304e0cdc3813584d87a6cf3e40c6119240a5e09ae92e650f5e", - "KeyExpiry": "2026-11-09T09:29:39Z", - "DiscoKey": "discokey:7522b0531b5612e3170073daedefc46bc57348b91dce68cc9e07e9d087c43654", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:48794", - "10.65.0.27:48794", - "172.17.0.1:48794", - "172.18.0.1:48794", - "172.19.0.1:48794", - "172.20.0.1:48794", - "172.21.0.1:48794", - "172.22.0.1:48794", - "172.23.0.1:48794", - "172.24.0.1:48794", - "172.25.0.1:48794", - "172.26.0.1:48794", - "172.27.0.1:48794" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:29:39.725982612Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5862924467252071, - "StableID": "nL1iXs5Lnn11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:1f2c54610b5c60ba0546dd2bcd5fc248ef5cdbf8949dd63e2f18f0856bb04514", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:38f7d9eda3690c65f595e42a0b2ed1b62379bb50523217184d143ab5c5691a5b", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:52155", - "10.65.0.27:52155", - "172.17.0.1:52155", - "172.18.0.1:52155", - "172.19.0.1:52155", - "172.20.0.1:52155", - "172.21.0.1:52155", - "172.22.0.1:52155", - "172.23.0.1:52155", - "172.24.0.1:52155", - "172.25.0.1:52155", - "172.26.0.1:52155", - "172.27.0.1:52155" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:29:40.256259971Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3805678568696981, - "StableID": "nrMYBvibiW11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:86c8345c3f407d8c6754e1ca156e1e299ad3b89dcb1d7aa937a6fa86cf03e532", - "KeyExpiry": "2026-11-09T09:29:40Z", - "DiscoKey": "discokey:4c23e8fbff3d7396d8260e6958541aa6749b7cc97d868d7a020ab6568befc97a", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:50255", - "10.65.0.27:50255", - "172.17.0.1:50255", - "172.18.0.1:50255", - "172.19.0.1:50255", - "172.20.0.1:50255", - "172.21.0.1:50255", - "172.22.0.1:50255", - "172.23.0.1:50255", - "172.24.0.1:50255", - "172.25.0.1:50255", - "172.26.0.1:50255", - "172.27.0.1:50255" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:29:40.787905965Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5947777451816847": { - "ID": 5947777451816847, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - } - }} -} diff --git a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-dst-duplicate-tags.hujson b/hscontrol/policy/v2/testdata/sshtest_results/sshtest-dst-duplicate-tags.hujson deleted file mode 100644 index 8e465495a..000000000 --- a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-dst-duplicate-tags.hujson +++ /dev/null @@ -1,21903 +0,0 @@ -// sshtest-dst-duplicate-tags -// -// sshTests dst contains duplicate tag -// -// Nodes with filter rules: 15 of 15 -// Captured at: 2026-05-13T09:30:16Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "sshtest-dst-duplicate-tags", - "description": "sshTests dst contains duplicate tag", - "category": "sshtest", - "captured_at": "2026-05-13T09:30:16.848261709Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n{\n\t\"category\": \"sshtest\",\n\t\"description\": \"sshTests dst contains duplicate tag\",\n\t\"id\": \"sshtest-dst-duplicate-tags\",\n\t\"policy\": {\"ssh\": [{\n\t\t\"action\": \"accept\",\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": [\"thor@example.org\"],\n\t\t\"users\": [\"root\"]\n\t}], \"sshTests\": [{\n\t\t\"accept\": [\"root\"],\n\t\t\"dst\": [\"tag:server\", \"tag:server\"],\n\t\t\"src\": \"thor@example.org\"\n\t}], \"tagOwners\": {\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/ssh-edges/sshtest-dst-duplicate-tags.hujson", - "full_policy": { - "ssh": [{ - "action": "accept", - "dst": ["tag:server"], - "src": ["thor@example.org"], - "users": ["root"] - }], - "sshTests": [{ - "accept": ["root"], - "dst": ["tag:server", "tag:server"], - "src": "thor@example.org" - }], - "tagOwners": {"tag:prod": ["odin@example.com"], "tag:server": ["odin@example.com"]} - } - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": [] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": [] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": [] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 6635437266779190, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "MachineKey": "mkey:edb0fca405f47ebe38ca05c073534e3ca50b655031fe9ea0d9f932c152dda818", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": [{ - "principals": [{"nodeIP": "100.64.0.17"}, {"nodeIP": "fd7a:115c:a1e0::11"}], - "sshUsers": {"root": "root"}, - "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - } - }]}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "6635437266779190": { - "ID": 6635437266779190, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }, - "ssh_rules": [{ - "principals": [{"nodeIP": "100.64.0.17"}, {"nodeIP": "fd7a:115c:a1e0::11"}], - "sshUsers": {"root": "root"}, - "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - } - }] - }, "blastoise": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 220059800127697, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:22.487425695Z", - "Tags": ["tag:exit", "tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "MachineKey": "mkey:90d79ada9ae5e6f735ec96d6551ba3fdee4d715dbef6c12892b07042b7237330", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "220059800127697": { - "ID": 220059800127697, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "bulbasaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "MachineKey": "mkey:5c5ba093e480d67dce27283e6649b1c1b16139393ece212c33c79620d8f62872", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "charmander": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1099277371122160, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:20.856238896Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "MachineKey": "mkey:0279ea735236deb1a45dad38dc01e20fc68b67c0c95c95cafe26d3023a8bfe44", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1099277371122160": { - "ID": 1099277371122160, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }, "1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "fearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 5602755481149455, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:23.630307934Z", - "Tags": ["tag:fearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "MachineKey": "mkey:d1154bde5c178198e6e8505962ee21804e3d766d1651ad980ea677528bc5b93c", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5602755481149455": { - "ID": 5602755481149455, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - } - }, "ivysaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "MachineKey": "mkey:c34158ddf9728cc7be60f62e351a9342738ceb57ba20058da89350d1fb9ed159", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "kakuna": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 8431358078059330, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "MachineKey": "mkey:4fb42d47ba8b660f06c81ca25e11233366908ac121525cc6c3667d29b1195814", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "8431358078059330": { - "ID": 8431358078059330, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - } - }, "pidgeotto": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1493384375532153, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:20.319592339Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "MachineKey": "mkey:60ad84a05ed643666b7b0c6bc44585400fa80e517e00adcd8f95403e30346111", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1493384375532153": { - "ID": 1493384375532153, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "pidgey": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 3628307374917488, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:19.604495837Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "MachineKey": "mkey:d22c37231a8232179fe85efb2da54f8a9d4d20fa9d384d721387eb439461fc3f", - "Peers": [{ - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3628307374917488": { - "ID": 3628307374917488, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "raticate": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 12685843844750, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "MachineKey": "mkey:a320d24b66814ccc76bdc3cddf6b95f9f4a467e166fb9c75b294635c456c384c", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "12685843844750": { - "ID": 12685843844750, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "rattata": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 2691709711896678, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "MachineKey": "mkey:98b3f0cecb218d4216107264c62e406d7341383c2bc23bdd2c7d44aa3678637e", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2691709711896678": { - "ID": 2691709711896678, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "spearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 7367908405015496, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:23.04063128Z", - "Tags": ["tag:spearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "MachineKey": "mkey:75757ab6de2cb3f822350faa42655f580e6cb6181fc71e81167d064706a89772", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "7367908405015496": { - "ID": 7367908405015496, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - } - }, "squirtle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 7395883338911835, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:30:24.35024234Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "MachineKey": "mkey:3ba5f17cf7f47c9df0932134e81b4967449de6e683bd0fc647bf725ff1a9bc33", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "7395883338911835": { - "ID": 7395883338911835, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }} - } - }, "venusaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "MachineKey": "mkey:bd48e5981fcc8a3224972d235adf5c0fc50567fa491291d3d0dd8abd050c3e08", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "weedle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 156123256026230, - "StableID": "nK3wWQ6iD211CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 156123256026230, - "Key": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "DiscoKey": "discokey:e315d2aa2650c181b0e7e8abe62aed80b5f6a8b39b3f5f590a3a6e81d7994a4e", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:59133", - "10.65.0.27:59133", - "172.17.0.1:59133", - "172.18.0.1:59133", - "172.19.0.1:59133", - "172.20.0.1:59133", - "172.21.0.1:59133", - "172.22.0.1:59133", - "172.23.0.1:59133", - "172.24.0.1:59133", - "172.25.0.1:59133", - "172.26.0.1:59133", - "172.27.0.1:59133" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:30:24.961232221Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:35bbd44adc1e0017f0f3cb5dc15c25172e8e0873f8475470e8716c906e85701c", - "MachineKey": "mkey:65e9067aa197f79f8903cd68b0d43a899472813b219cc5697e6a026b67b1475a", - "Peers": [{ - "ID": 3628307374917488, - "StableID": "nwJfAyUGLV11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8587a6f7374ca011a9ac6468627b921cfa75948a6fb3f0b04df5ffc7e4822b5b", - "DiscoKey": "discokey:5291592b301d037d3573ecb86b8ffafda3907780e8ffc5edb50e85035ed66a74", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:60776", - "10.65.0.27:60776", - "172.17.0.1:60776", - "172.18.0.1:60776", - "172.19.0.1:60776", - "172.20.0.1:60776", - "172.21.0.1:60776", - "172.22.0.1:60776", - "172.23.0.1:60776", - "172.24.0.1:60776", - "172.25.0.1:60776", - "172.26.0.1:60776", - "172.27.0.1:60776" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:30:19.604495837Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1493384375532153, - "StableID": "nJPrQAgMfC11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb5bb1cdb0ff33b7e7bee6ec6b139f6fafb9f188c6bab4243677c47cc5a70831", - "DiscoKey": "discokey:13886cbf8a432412327a73ba106d682f72fbde38b2771ab03c66baabdfaee42f", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:52709", - "10.65.0.27:52709", - "172.17.0.1:52709", - "172.18.0.1:52709", - "172.19.0.1:52709", - "172.20.0.1:52709", - "172.21.0.1:52709", - "172.22.0.1:52709", - "172.23.0.1:52709", - "172.24.0.1:52709", - "172.25.0.1:52709", - "172.26.0.1:52709", - "172.27.0.1:52709" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:30:20.319592339Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1099277371122160, - "StableID": "ns8Hzq9sa911CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:6ead0b971a84bb0155d90e0ebd3eb6e7d7353739feb2acb825c5deb763631d53", - "DiscoKey": "discokey:a4ae1f8f20ee5f9b386feb4b92b080b07ae8d53d1cc019fa7c7761bdb2a7df70", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:43004", - "10.65.0.27:43004", - "172.17.0.1:43004", - "172.18.0.1:43004", - "172.19.0.1:43004", - "172.20.0.1:43004", - "172.21.0.1:43004", - "172.22.0.1:43004", - "172.23.0.1:43004", - "172.24.0.1:43004", - "172.25.0.1:43004", - "172.26.0.1:43004", - "172.27.0.1:43004" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:30:20.856238896Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2691709711896678, - "StableID": "ndnyuge52N11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2f0f9aa40c11d6caefbf393a5ae8eeb99f47053abac6b79681f8aa1b67e98769", - "DiscoKey": "discokey:29ee71a95c71f6ed791da21b42c0c819f0b95368406800121696acf5ffca1745", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:45143", - "10.65.0.27:45143", - "172.17.0.1:45143", - "172.18.0.1:45143", - "172.19.0.1:45143", - "172.20.0.1:45143", - "172.21.0.1:45143", - "172.22.0.1:45143", - "172.23.0.1:45143", - "172.24.0.1:45143", - "172.25.0.1:45143", - "172.26.0.1:45143", - "172.27.0.1:45143" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:30:21.393986895Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 12685843844750, - "StableID": "n9tdafEk6111CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:8e9317595a76d9da92f070ba866cbbfd950bebd0bb2a22da60dd5f548d9e7739", - "DiscoKey": "discokey:20045d9d345983c979fadb65dd0db6620eb191751458cac1582766f33d25be41", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:58390", - "10.65.0.27:58390", - "172.17.0.1:58390", - "172.18.0.1:58390", - "172.19.0.1:58390", - "172.20.0.1:58390", - "172.21.0.1:58390", - "172.22.0.1:58390", - "172.23.0.1:58390", - "172.24.0.1:58390", - "172.25.0.1:58390", - "172.26.0.1:58390", - "172.27.0.1:58390" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:30:21.965086659Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 220059800127697, - "StableID": "nJfiyfbfi211CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f82d10ad4a8afe9aeee81c4c1dcf49cda2f43da67e83bb43dd02f4616d907911", - "DiscoKey": "discokey:9c598e24bacf2aa5dbcdfa20934c7082c2d423abf9aa422d163284b41be6ee49", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:41839", - "10.65.0.27:41839", - "172.17.0.1:41839", - "172.18.0.1:41839", - "172.19.0.1:41839", - "172.20.0.1:41839", - "172.21.0.1:41839", - "172.22.0.1:41839", - "172.23.0.1:41839", - "172.24.0.1:41839", - "172.25.0.1:41839", - "172.26.0.1:41839", - "172.27.0.1:41839" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:30:22.487425695Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 7367908405015496, - "StableID": "nw4zXgTwXz11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b5171dd8248b6600e124ab8a373ad2474d1422ebf0a43a7f93e526e1e45ae774", - "DiscoKey": "discokey:32447222761bd26fc98eb5000a8954e03b20b5456e2f40230debc4e3bd71dc5f", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:44778", - "10.65.0.27:44778", - "172.17.0.1:44778", - "172.18.0.1:44778", - "172.19.0.1:44778", - "172.20.0.1:44778", - "172.21.0.1:44778", - "172.22.0.1:44778", - "172.23.0.1:44778", - "172.24.0.1:44778", - "172.25.0.1:44778", - "172.26.0.1:44778", - "172.27.0.1:44778" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:30:23.04063128Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5602755481149455, - "StableID": "nJ4o8PtVkk11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4d8d2db2cb821338ea64e3375fc8e1048d0b9fc371c24e4d176cd629ef522065", - "DiscoKey": "discokey:400d522c5a33d5f017772b6608482cf826411a0f597b22b01e50664e6835e35a", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:37413", - "10.65.0.27:37413", - "172.17.0.1:37413", - "172.18.0.1:37413", - "172.19.0.1:37413", - "172.20.0.1:37413", - "172.21.0.1:37413", - "172.22.0.1:37413", - "172.23.0.1:37413", - "172.24.0.1:37413", - "172.25.0.1:37413", - "172.26.0.1:37413", - "172.27.0.1:37413" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:30:23.630307934Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 7395883338911835, - "StableID": "nYPXeDKckz11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c89610742a89888e4ceac7e52b6aa315db690c98ada622f726b3a4242e9b8d3f", - "DiscoKey": "discokey:00b9780e7c8d9deeaf1a27ce309babca928035af7dd7fa200c57271a67bf820a", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:35503", - "10.65.0.27:35503", - "172.17.0.1:35503", - "172.18.0.1:35503", - "172.19.0.1:35503", - "172.20.0.1:35503", - "172.21.0.1:35503", - "172.22.0.1:35503", - "172.23.0.1:35503", - "172.24.0.1:35503", - "172.25.0.1:35503", - "172.26.0.1:35503", - "172.27.0.1:35503" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:30:24.35024234Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 8431358078059330, - "StableID": "nsmfXpUaq821CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:63133f6c88593acd3b63105c7176719ce717f2e66b9b7657c0aa3acc4557316d", - "DiscoKey": "discokey:d204d313a545b75f35dbf27c5fd8924b694c32b6d1d7622f8c665bae2c052373", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:50334", - "10.65.0.27:50334", - "172.17.0.1:50334", - "172.18.0.1:50334", - "172.19.0.1:50334", - "172.20.0.1:50334", - "172.21.0.1:50334", - "172.22.0.1:50334", - "172.23.0.1:50334", - "172.24.0.1:50334", - "172.25.0.1:50334", - "172.26.0.1:50334", - "172.27.0.1:50334" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:30:25.669266846Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6635437266779190, - "StableID": "nme7HkgCpt11CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cbf7c7ea6bd03996c13db4de2b558efbbd5cf8536f41cc76424b15a8cce8ed6b", - "DiscoKey": "discokey:b83c63aafa2ac23ca7968015176fdf00a90a94ddc1cc8402ef0b72afc8a78706", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:45684", - "10.65.0.27:45684", - "172.17.0.1:45684", - "172.18.0.1:45684", - "172.19.0.1:45684", - "172.20.0.1:45684", - "172.21.0.1:45684", - "172.22.0.1:45684", - "172.23.0.1:45684", - "172.24.0.1:45684", - "172.25.0.1:45684", - "172.26.0.1:45684", - "172.27.0.1:45684" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:30:26.20463116Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6048256030654495, - "StableID": "nkqe9wRGEp11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:a2661c0992e6f6f36c961226b6c951c0324de2a6d2279d7e43c3a6fed43f9652", - "KeyExpiry": "2026-11-09T09:30:26Z", - "DiscoKey": "discokey:3ebad77570ee23f4a75003d66ebe44faa51879ec277d967f1df088231e820c28", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:45887", - "10.65.0.27:45887", - "172.17.0.1:45887", - "172.18.0.1:45887", - "172.19.0.1:45887", - "172.20.0.1:45887", - "172.21.0.1:45887", - "172.22.0.1:45887", - "172.23.0.1:45887", - "172.24.0.1:45887", - "172.25.0.1:45887", - "172.26.0.1:45887", - "172.27.0.1:45887" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:30:26.725360455Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 8234239497029749, - "StableID": "nvuPXWWJJ721CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:180fb2b409872a185fa9447ecfbb845e0154f6c7f143eb6ca09e3d2bd6ddbb17", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:7e5bb9a482709932c2f64cb02779a5a9c71b33777a842c47041a0fc713b9587a", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:50553", - "10.65.0.27:50553", - "172.17.0.1:50553", - "172.18.0.1:50553", - "172.19.0.1:50553", - "172.20.0.1:50553", - "172.21.0.1:50553", - "172.22.0.1:50553", - "172.23.0.1:50553", - "172.24.0.1:50553", - "172.25.0.1:50553", - "172.26.0.1:50553", - "172.27.0.1:50553" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:30:27.260403572Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 3225617935357335, - "StableID": "nAUbLnWtBS11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:05fb5c7def2bd52a36b410573911e9e7309ce75d545fb99011ceb2ed571d5d5d", - "KeyExpiry": "2026-11-09T09:30:27Z", - "DiscoKey": "discokey:0dde8e43d786368333282af84458203134ee89de681daae268aea547302eaa39", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:57338", - "10.65.0.27:57338", - "172.17.0.1:57338", - "172.18.0.1:57338", - "172.19.0.1:57338", - "172.20.0.1:57338", - "172.21.0.1:57338", - "172.22.0.1:57338", - "172.23.0.1:57338", - "172.24.0.1:57338", - "172.25.0.1:57338", - "172.26.0.1:57338", - "172.27.0.1:57338" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:30:27.807949242Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "156123256026230": { - "ID": 156123256026230, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }} -} diff --git a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv4.hujson b/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv4.hujson deleted file mode 100644 index 591c2f51e..000000000 --- a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv4.hujson +++ /dev/null @@ -1,18758 +0,0 @@ -// sshtest-malformed-dst-bare-ipv4 -// -// sshTests dst with bare IPv4 literal -// -// Nodes with filter rules: 15 of 15 -// Captured at: 2026-05-13T10:48:43Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "sshtest-malformed-dst-bare-ipv4", - "description": "sshTests dst with bare IPv4 literal", - "category": "sshtest", - "captured_at": "2026-05-13T10:48:43.837501769Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n{\n\t\"category\": \"sshtest\",\n\t\"description\": \"sshTests dst with bare IPv4 literal\",\n\t\"id\": \"sshtest-malformed-dst-bare-ipv4\",\n\t\"options\": {\"expect_api_error\": true},\n\t\"policy\": {\"grants\": [{\"dst\": [\"*\"], \"ip\": [\"*\"], \"src\": [\"*\"]}], \"ssh\": [{\n\t\t\"action\": \"accept\",\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": [\"autogroup:member\"],\n\t\t\"users\": [\"root\"]\n\t}], \"sshTests\": [{\n\t\t\"accept\": [\"root\"],\n\t\t\"dst\": [\"100.64.0.16\"],\n\t\t\"src\": \"odin@example.com\"\n\t}], \"tagOwners\": {\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/sshtest-edges-baremip/sshtest-malformed-dst-bare-ipv4.hujson", - "full_policy": { - "grants": [{"dst": ["*"], "ip": ["*"], "src": ["*"]}], - "ssh": [{ - "action": "accept", - "dst": ["tag:server"], - "src": ["autogroup:member"], - "users": ["root"] - }], - "sshTests": [ - {"accept": ["root"], "dst": ["100.64.0.16"], "src": "odin@example.com"} - ], - "tagOwners": {"tag:prod": ["odin@example.com"], "tag:server": ["odin@example.com"]} - } - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": [] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": [] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": [] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 8722759479154595, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "MachineKey": "mkey:306c55ab2ca3b6e3418717b16619e21926dcffa2611d5965ed5dbce0b19b3863", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": [{"principals": [ - {"nodeIP": "100.64.0.17"}, - {"nodeIP": "100.64.0.18"}, - {"nodeIP": "100.64.0.19"}, - {"nodeIP": "fd7a:115c:a1e0::11"}, - {"nodeIP": "fd7a:115c:a1e0::12"}, - {"nodeIP": "fd7a:115c:a1e0::13"} - ], "sshUsers": {"root": "root"}, "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - }}]}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "8722759479154595": { - "ID": 8722759479154595, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }, - "ssh_rules": [{"principals": [ - {"nodeIP": "100.64.0.17"}, - {"nodeIP": "100.64.0.18"}, - {"nodeIP": "100.64.0.19"}, - {"nodeIP": "fd7a:115c:a1e0::11"}, - {"nodeIP": "fd7a:115c:a1e0::12"}, - {"nodeIP": "fd7a:115c:a1e0::13"} - ], "sshUsers": {"root": "root"}, "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - }}] - }, "blastoise": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1986105523559038, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:53.034821277Z", - "Tags": ["tag:exit", "tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "MachineKey": "mkey:5f243ec1cddf2ef9bb90da7ddd78cdd56babe27b3131272afdb505a21f55d86b", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1986105523559038": { - "ID": 1986105523559038, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "bulbasaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "MachineKey": "mkey:fe88c3803b82174e5c5e0263b6aa2575c667b97bca69cb569c22b32120869f31", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "charmander": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1638953534304319, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:51.424310318Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "MachineKey": "mkey:88de3e7777edb60bb959f3ec791c9e79ae3463e549c37cdd7c2fad97b6cd0615", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1638953534304319": { - "ID": 1638953534304319, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "fearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 2159651897938158, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:54.126510557Z", - "Tags": ["tag:fearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "MachineKey": "mkey:9dbdb62aca089eed843210244b22603cbd48c2b138dfd3851db6996b33a6f376", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2159651897938158": { - "ID": 2159651897938158, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "ivysaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "MachineKey": "mkey:db813f7e596172e9b632fbe69cdf33d69014075a5c6ee5175e39acaa4dab8701", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "kakuna": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 2367360492594434, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "MachineKey": "mkey:25338232ba5bc2801a583c4a8f0942f0d4094faefeb5fe7d21caa92ed676311e", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2367360492594434": { - "ID": 2367360492594434, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "pidgeotto": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 772085588111436, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:50.891907715Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "MachineKey": "mkey:2dd462b76957c5cd6d3354f5b9b0a6f8517b040d2788e8ec3625bd8853be451b", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "772085588111436": { - "ID": 772085588111436, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - } - }, "pidgey": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 6200298225266045, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:50.362795491Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "MachineKey": "mkey:83aa5bc784977d87ee4ca20a505b454b1acd4d74244beb4cb465b2e804e12905", - "Peers": [{ - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "6200298225266045": { - "ID": 6200298225266045, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - } - }, "raticate": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 3519823077666716, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "MachineKey": "mkey:c8d911aa0beb92048817b9af5f30ddcb31492ef289ce7a3e533dcf5a29d4c446", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3519823077666716": { - "ID": 3519823077666716, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "rattata": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 3765147146864741, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "MachineKey": "mkey:5626ffd550807dc51983e6d38c8bdb3a085434430d65d5bb7a33f036462e527a", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3765147146864741": { - "ID": 3765147146864741, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "spearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 5130804425872062, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:53.586230203Z", - "Tags": ["tag:spearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "MachineKey": "mkey:ad41105a1ab99f000c660872c887ae19ef828614b52fd434589a50456776524f", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5130804425872062": { - "ID": 5130804425872062, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - } - }, "squirtle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1349337447109109, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:48:54.655381755Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "MachineKey": "mkey:c0a3bf6535f0d30922c2ddccd6fe4e4468d1df2dcbc4748e01fda852e632b230", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "1349337447109109": { - "ID": 1349337447109109, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "venusaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "MachineKey": "mkey:e7d0a43386db8802a3d53edb64e4d914ea8ce9433ee8eecde0904afbfd077217", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "weedle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5882431403440684, - "StableID": "nuRZgtVAwn11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 5882431403440684, - "Key": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "DiscoKey": "discokey:f8fa287f5363f5bdf521d0b8e84f40c36e1bf3c5465949d12433d213e7e4f718", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:40111", "10.65.0.27:40111", "172.17.0.1:40111"], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:48:55.202876425Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a398e17e1c282f84c5f6b7cfb7446ecaccec8951981b1fd55c1d8e6368d9fa42", - "MachineKey": "mkey:4705e9c98098b3df65701651d41e77c42fdfc943b546f111113041ad3e250f2f", - "Peers": [{ - "ID": 6200298225266045, - "StableID": "nkL6FbK8Rq11CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:caa25e6f3964747f2819e3fda3f7c706295b6a49c664b8e2ea89c3969bdbda2b", - "DiscoKey": "discokey:77ec9f8fce0ede08d9d1a5b029f8a0cae296110933405e7519506e79e073333d", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:48042", "10.65.0.27:48042", "172.17.0.1:48042"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:48:50.362795491Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 772085588111436, - "StableID": "nwaEYzNg2711CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:1c2079dc508afa576b94809475a2ac47d3a36c6e3f735ef1b55c27ffcec1a373", - "DiscoKey": "discokey:8cb4612d5f0e3c5749ae5ed0fbbbe1785c2932e55ba34fec31e0be6d96f2607a", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:55637", "10.65.0.27:55637", "172.17.0.1:55637"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:48:50.891907715Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 1638953534304319, - "StableID": "n22eVkXHoD11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c1462e2a6dc4d8abe004dff315631a69b43887f020c6f19139bda5e98be5f75c", - "DiscoKey": "discokey:140fac845fae8282c5c0766d4640b23d93aa54c0b9fa01194f57b1a38629c901", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:44209", "10.65.0.27:44209", "172.17.0.1:44209"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:48:51.424310318Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 3765147146864741, - "StableID": "ncADEo2FQW11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:38fe0b9e71caeb8135dac0fbe880b7e748009c26f0abb43b3527cb72212b175b", - "DiscoKey": "discokey:198022f74d83de950d9085d4fc72fd3fbc42e6f9db4452df75e4ee3820310271", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:49338", "10.65.0.27:49338", "172.17.0.1:49338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:48:51.967866113Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3519823077666716, - "StableID": "nBpiJTn8VU11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e0d87ab5fbdc9d4e34413f797214d6ac38849e6dc6374e12d90612cba7f8a069", - "DiscoKey": "discokey:0a60fc1cddb12dbd09d89e6b51a4ce9a2411d5e9c839caf89eb396dd0df44851", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:38235", "10.65.0.27:38235", "172.17.0.1:38235"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:48:52.512631024Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 1986105523559038, - "StableID": "nPm32DdWWG11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:5102f677c7b7bb670223630e40cbaea4e932d7d0a62f334e3d39dda92d3fdb77", - "DiscoKey": "discokey:ce969702ba57154bad7015018753d95c981ffd8a8be1de79cea3e61e0bc0297a", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:49208", "10.65.0.27:49208", "172.17.0.1:49208"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:48:53.034821277Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5130804425872062, - "StableID": "nFwzVrXk4h11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:908f9f71cf33139bd9e32b7628bef012d466eb003ded2d8d99417a85cb294047", - "DiscoKey": "discokey:ceb56c41917fe22122cde49a826c033fede711c180bbe5331d982702739fbc2b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55194", "10.65.0.27:55194", "172.17.0.1:55194"], - "HomeDERP": 8, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:48:53.586230203Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2159651897938158, - "StableID": "nunjqoP7sH11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:e9c626ba7ebfdb3c7dde942b025e7c1e785e5346f6406556926a73daeeb1c816", - "DiscoKey": "discokey:cbcb42ec1c6942dc1516dbd7fa3ccbc14b63f19487e6bdce2f989034bcf02962", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:43026", "10.65.0.27:43026", "172.17.0.1:43026"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:48:54.126510557Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 1349337447109109, - "StableID": "nWqVcno7YB11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:53b27d1f1c5b6ac9e0ee6390391b0a3635092481c5727fc7ea696388d123a02f", - "DiscoKey": "discokey:081d07001df2cca09159b3071e5ffcd20ce392e9fa11610354247a31d4f09f59", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:54340", "10.65.0.27:54340", "172.17.0.1:54340"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:48:54.655381755Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2367360492594434, - "StableID": "nRLx5fYBVK11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:191d2ceeca75f0e2829f5296e0321d7af0b1a9b19888776491e988a1ede7311a", - "DiscoKey": "discokey:fb30d96a84b67697b2c5ba4a27262251e2245a56a402f546b9b2bb8f84f9291d", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:40035", "10.65.0.27:40035", "172.17.0.1:40035"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:48:55.738626975Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 8722759479154595, - "StableID": "nG3DVp6Z7B21CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ce0976880a1f7f6dc7475d9cfbc62750f45c14025cea8ac3cbf9c24afba8ba23", - "DiscoKey": "discokey:e0112828e95db0bb1c0982dca3ef7ce2e574c0d18c0462ed202244eac928fc71", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:40653", "10.65.0.27:40653", "172.17.0.1:40653"], - "HomeDERP": 26, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:48:56.276059496Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 8789958571042068, - "StableID": "nuyB8nJzdB21CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:d5f6fca3ad79e6e1c4176b9916873989f27d90b04f1b821734ddac3ea33db221", - "KeyExpiry": "2026-11-09T10:48:56Z", - "DiscoKey": "discokey:29333efc1ed524edb99c6286282bb51a73104d7f9fe6c8e43aa5f607c9efce78", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:50705", "10.65.0.27:50705", "172.17.0.1:50705"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:48:56.81026368Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 4397469093301501, - "StableID": "nL2HMG4dLb11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:b3cb7fa4c23188ce2a8ad836a8b252fa1526358fba756bd509adbb33ab240f5c", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:73765b4c398fc506d93c97edceb5bd28515a96c711012398c1d91fcf33592012", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:45003", "10.65.0.27:45003", "172.17.0.1:45003"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:48:57.352517284Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7822324820044157, - "StableID": "nAw947Dk5421CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:bbb1fc9a6f7e90fb22a04203694abe55b821617df286d129da214c42b98ea319", - "KeyExpiry": "2026-11-09T10:48:57Z", - "DiscoKey": "discokey:a733427a6a060e36d67831e7b58bc300b184092bf1598e0d517c4a413de3ee58", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:53974", "10.65.0.27:53974", "172.17.0.1:53974"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:48:57.903377063Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5882431403440684": { - "ID": 5882431403440684, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - } - }} -} diff --git a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv6.hujson b/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv6.hujson deleted file mode 100644 index d3b362bf6..000000000 --- a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-malformed-dst-bare-ipv6.hujson +++ /dev/null @@ -1,18737 +0,0 @@ -// sshtest-malformed-dst-bare-ipv6 -// -// sshTests dst with bare IPv6 literal -// -// Nodes with filter rules: 15 of 15 -// Captured at: 2026-05-13T10:49:43Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "sshtest-malformed-dst-bare-ipv6", - "description": "sshTests dst with bare IPv6 literal", - "category": "sshtest", - "captured_at": "2026-05-13T10:49:43.202748457Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "error": true, - "input": { - "api_response_code": 400, - "api_response_body": {"message": "test(s) failed"}, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n{\n\t\"category\": \"sshtest\",\n\t\"description\": \"sshTests dst with bare IPv6 literal\",\n\t\"id\": \"sshtest-malformed-dst-bare-ipv6\",\n\t\"options\": {\"expect_api_error\": true},\n\t\"policy\": {\"grants\": [{\"dst\": [\"*\"], \"ip\": [\"*\"], \"src\": [\"*\"]}], \"ssh\": [{\n\t\t\"action\": \"accept\",\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": [\"autogroup:member\"],\n\t\t\"users\": [\"root\"]\n\t}], \"sshTests\": [{\n\t\t\"accept\": [\"root\"],\n\t\t\"dst\": [\"fd7a:115c:a1e0::10\"],\n\t\t\"src\": \"odin@example.com\"\n\t}], \"tagOwners\": {\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/sshtest-edges-baremip/sshtest-malformed-dst-bare-ipv6.hujson", - "full_policy": { - "grants": [{"dst": ["*"], "ip": ["*"], "src": ["*"]}], - "ssh": [{ - "action": "accept", - "dst": ["tag:server"], - "src": ["autogroup:member"], - "users": ["root"] - }], - "sshTests": [{ - "accept": ["root"], - "dst": ["fd7a:115c:a1e0::10"], - "src": "odin@example.com" - }], - "tagOwners": {"tag:prod": ["odin@example.com"], "tag:server": ["odin@example.com"]} - } - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": [] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": [] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": [] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 334927240877647, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "MachineKey": "mkey:a30c70c9ed64003a2cccda14180de7a17d10f0a3abca514727971b3ea7964073", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "334927240877647": { - "ID": 334927240877647, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "blastoise": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 3719379002270468, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:48.451607497Z", - "Tags": ["tag:exit", "tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "MachineKey": "mkey:d58d4f7bcf3b7fd17add9d81732861fbfab0a8fcf0feca16489265da8127c549", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3719379002270468": { - "ID": 3719379002270468, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "bulbasaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "MachineKey": "mkey:58c354c8cbb19788094e5a4e666e159bb45f81a41d04403b3d4be56869735d34", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "charmander": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 3017773562694856, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:46.825444131Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "MachineKey": "mkey:ec51a4a247f298e2b6906444cb62585ae34819b8d2a8f8ed5c213f50915f332a", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3017773562694856": { - "ID": 3017773562694856, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "fearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 5717007869107976, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:49.539044425Z", - "Tags": ["tag:fearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "MachineKey": "mkey:4bd1aed763aaa00eae2f54432ea206530fbc32be68b851d156ca5b8403c55916", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5717007869107976": { - "ID": 5717007869107976, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }} - } - }, "ivysaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "MachineKey": "mkey:6fa10154e25821a8301386b773b6fc501cc434933e0e09314a8d660652949e3e", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "kakuna": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 5639247385574474, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "MachineKey": "mkey:8380bcff507ce2998978a3948f4571514ccd9951357fe9524deba22ba829a706", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5639247385574474": { - "ID": 5639247385574474, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }} - } - }, "pidgeotto": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 4038382799288908, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:46.301030305Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "MachineKey": "mkey:07ecb2727121b6e23340ad8edec8ff43acd1c18ddd281d38e5d578187f571426", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4038382799288908": { - "ID": 4038382799288908, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "pidgey": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 859261059412166, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:45.772657121Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "MachineKey": "mkey:c33b20f01ed76d1ab2568383b634016a633368ff428506330a369d1d5f17c448", - "Peers": [{ - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "859261059412166": { - "ID": 859261059412166, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - } - }, "raticate": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 5408923338656444, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "MachineKey": "mkey:73d0b57dae0b149e5e83750fefa11506a6e689298bd5691ceb2ccc0c6afcae10", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "5408923338656444": { - "ID": 5408923338656444, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - } - }, "rattata": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 841031876026915, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "MachineKey": "mkey:cabb074d03cdef38e6bb8f95453369749a4b48fce47d534efadffc0c75833a2c", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "841031876026915": { - "ID": 841031876026915, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }} - } - }, "spearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 4938745266024003, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:48.99059142Z", - "Tags": ["tag:spearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "MachineKey": "mkey:b050d3695c3530c7586d127345a16efef3b55779aaf6c195991339bba11d6645", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "4938745266024003": { - "ID": 4938745266024003, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }} - } - }, "squirtle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 2681791575458757, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T10:49:50.077242292Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "MachineKey": "mkey:460ecd09ab8843ace60a2481c54a151a12c5b68246de0a615b5f2c90d965cb3d", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2681791575458757": { - "ID": 2681791575458757, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "venusaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "MachineKey": "mkey:30ee23d58bcf4841e01e65b99d30fc7158923a5db1d8bd1aa62fa14142e42558", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "weedle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6843923602848287, - "StableID": "n68NsXGdSv11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 6843923602848287, - "Key": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "DiscoKey": "discokey:e9aca9a712d8b23b1eb95b03b046456a2e75c67dca65a58baa026b9c88267831", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": ["77.164.248.136:49062", "10.65.0.27:49062", "172.17.0.1:49062"], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T10:49:50.611547224Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:35a55ec66e4cda8326413883a0811dfd80ac59d0d3914758bd050f7dbcc7407f", - "MachineKey": "mkey:7de620dd4547fee92dbb36341d8829fcfca8655af535aee239d41dc4e7bc4451", - "Peers": [{ - "ID": 859261059412166, - "StableID": "nuUJRCLAi711CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a7e877e42095005a66e6c1aab51ed3285f7d7cc62ccc078ad1d5217f9766a755", - "DiscoKey": "discokey:bf7ca0736125ef38e6f6b59d7c13ec464d326a7f9eaf2755b2c0e786a87d9e5b", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": ["77.164.248.136:56022", "10.65.0.27:56022", "172.17.0.1:56022"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T10:49:45.772657121Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 4038382799288908, - "StableID": "nDhVBAUzXY11CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:2d6413a67f6cfa2f0811ea5173d77cb82eea52a994848e24959d79f17685b976", - "DiscoKey": "discokey:de3507d6af75b68423e1c5dc49aecff2ad9aa9960b7adf5b8f08cda91fa33a44", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": ["77.164.248.136:47955", "10.65.0.27:47955", "172.17.0.1:47955"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T10:49:46.301030305Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 3017773562694856, - "StableID": "nh7cr4okZQ11CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:0ed22425f9400edd4fd7f9b6230fada69809c3f67c5d6e205ac0eb9003874076", - "DiscoKey": "discokey:cecc4ba5b5bec6c80fdbe0cb7284423408923db0827f81de7ba9d756d26b934a", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": ["77.164.248.136:34716", "10.65.0.27:34716", "172.17.0.1:34716"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T10:49:46.825444131Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 841031876026915, - "StableID": "nxFZuuUuZ711CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:40c5193c938376c182253108a8393e39db387193b50b5a03a504640b92525415", - "DiscoKey": "discokey:e7c83de9c4cc48f6392ec088db09d6967e1ccae6eb202ed929bc594b5a661200", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": ["77.164.248.136:37212", "10.65.0.27:37212", "172.17.0.1:37212"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T10:49:47.356999827Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 5408923338656444, - "StableID": "nVK4TAFiEj11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f270ee1ed3fdaa747b96e3392a25be3014c3b1d705dc821b832d712fc6ec1123", - "DiscoKey": "discokey:27ca093c2966725423323b41a97e26a816094c51fc6e814998cd6c02d004d26f", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": ["77.164.248.136:33332", "10.65.0.27:33332", "172.17.0.1:33332"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T10:49:47.918004223Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 3719379002270468, - "StableID": "n3Wu1DnW3W11CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4be2686873fed2b904d2b62fb3dcaa61b053ca2e8b2bb7be71974cdce4306870", - "DiscoKey": "discokey:8e144a7a9659a77eef738e00b4ff3752d5dce14b11b8bffefda1135826276c7c", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": ["77.164.248.136:57272", "10.65.0.27:57272", "172.17.0.1:57272"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T10:49:48.451607497Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 4938745266024003, - "StableID": "nikkDtTmZf11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:bff1dab85d012316bf0f494449937f3a626217a3f81f560d9771dca2b18ce028", - "DiscoKey": "discokey:7b76eb268d98f6279a6c473c43610738414da4d9972308ee11079fcb1505bc39", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": ["77.164.248.136:55316", "10.65.0.27:55316", "172.17.0.1:55316"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T10:49:48.99059142Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 5717007869107976, - "StableID": "nqhKRw6Fem11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:778958671603dd7ab7e6329ca20c7f129326c2b53b666e9a1ad110fb07ec4200", - "DiscoKey": "discokey:62ba0795a1a73a0e59aca58921ef4bf02dc562a67d72dcb09c763dbf9878441e", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": ["77.164.248.136:41514", "10.65.0.27:41514", "172.17.0.1:41514"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T10:49:49.539044425Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2681791575458757, - "StableID": "ngUFAo7bwM11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b3998f59a28c54d599dab7f93dca41491b68bf149bd4cdb7bfc6c2a12e43467e", - "DiscoKey": "discokey:2b408dcfd99442c220534cfe6cf009234106918bfa35cd155c4bc373110a2610", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": ["77.164.248.136:57338", "10.65.0.27:57338", "172.17.0.1:57338"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T10:49:50.077242292Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 5639247385574474, - "StableID": "nukXm3U23m11CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:f94eea7d87a9966dc52c708dd1d9b2358ba3967e333eab363b20056afeccc32f", - "DiscoKey": "discokey:8b3aa50839dbf30da90c85f2028cb66b27d0b99642ea15a012fcda7b8fb5146c", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": ["77.164.248.136:56076", "10.65.0.27:56076", "172.17.0.1:56076"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T10:49:51.149343502Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 334927240877647, - "StableID": "nk5aNJygc311CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:dc734d58cca95b35554d568625022525f4805fd9661485b56dd62c478b903961", - "DiscoKey": "discokey:08860ffc9ad49254248fc284273dc3f1e6cd4500003b3b4180836ac7b297e55c", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": ["77.164.248.136:53569", "10.65.0.27:53569", "172.17.0.1:53569"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T10:49:51.696657826Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 6999320821172825, - "StableID": "nvDJvmH1fw11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:5d1f86245681cd179cba56fc189351670f3f4614e12b161fb2b9dadad1795447", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:c6c54d8ca1c7739b766b9e220290d53920c0598ba35753f259b5493a7dcfb017", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": ["77.164.248.136:41270", "10.65.0.27:41270", "172.17.0.1:41270"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T10:49:52.231996754Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 3911180766115955, - "StableID": "nGfM626PYX11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:2ebeea6ee130392da77f4dc5e1166a0f819ac768b8d773808e8d454cd3a46169", - "KeyExpiry": "2026-11-09T10:49:52Z", - "DiscoKey": "discokey:5ffa1f5176b04a6e9c1b22114c7594f9557df41aae4c3c439444f29cbab27f62", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": ["77.164.248.136:33581", "10.65.0.27:33581", "172.17.0.1:33581"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T10:49:52.769240823Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 7952933030713537, - "StableID": "nNDJPm4u6521CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:e1d5f353569caba6c02adf8f7d450836332b84718111e65686b3d1e1af02cb6d", - "KeyExpiry": "2026-11-09T10:49:53Z", - "DiscoKey": "discokey:cbb9f7d452557afe0040be4401b3886943ad7a102a1117f1170aa343e6ae6a4b", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": ["77.164.248.136:58443", "10.65.0.27:58443", "172.17.0.1:58443"], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T10:49:53.299344535Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "6843923602848287": { - "ID": 6843923602848287, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }} - } - }} -} diff --git a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-multi-rule-disjoint-srcs.hujson b/hscontrol/policy/v2/testdata/sshtest_results/sshtest-multi-rule-disjoint-srcs.hujson deleted file mode 100644 index f2fa5c197..000000000 --- a/hscontrol/policy/v2/testdata/sshtest_results/sshtest-multi-rule-disjoint-srcs.hujson +++ /dev/null @@ -1,21904 +0,0 @@ -// sshtest-multi-rule-disjoint-srcs -// -// sshTests two entries disjoint srcs, overlap accept -// -// Nodes with filter rules: 15 of 15 -// Captured at: 2026-05-13T09:31:13Z -// tool version: tscap/dev -// schema version: 1 -{ - "schema_version": 1, - "test_id": "sshtest-multi-rule-disjoint-srcs", - "description": "sshTests two entries disjoint srcs, overlap accept", - "category": "sshtest", - "captured_at": "2026-05-13T09:31:13.08475786Z", - "tool_version": "tscap/dev", - "tailnet": "odin@example.com", - "input": { - "api_response_code": 200, - "tailnet": { - "dns": {"magic_dns": false, "nameservers": [], "search_paths": [], "split_dns": {}}, - "settings": {} - }, - "scenario_hujson": " \n \n \n \n{\n\t\"category\": \"sshtest\",\n\t\"description\": \"sshTests two entries disjoint srcs, overlap accept\",\n\t\"id\": \"sshtest-multi-rule-disjoint-srcs\",\n\t\"policy\": {\"ssh\": [{\n\t\t\"action\": \"accept\",\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": [\"thor@example.org\", \"freya@example.com\"],\n\t\t\"users\": [\"root\"]\n\t}], \"sshTests\": [{\n\t\t\"accept\": [\"root\"],\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": \"thor@example.org\"\n\t}, {\n\t\t\"accept\": [\"root\"],\n\t\t\"dst\": [\"tag:server\"],\n\t\t\"src\": \"freya@example.com\"\n\t}], \"tagOwners\": {\n\t\t\"tag:prod\": [\"odin@example.com\"],\n\t\t\"tag:server\": [\"odin@example.com\"]\n\t}},\n\t\"tailnet\": {\n\t\t\"dns\": {\"magic_dns\": false, \"nameservers\": [], \"search_paths\": [], \"split_dns\": {}},\n\t\t\"settings\": {}\n\t},\n\t\"topology\": \"../_topologies/grant.hujson\"\n}\n", - "scenario_path": "scenarios/ssh-edges/sshtest-multi-rule-disjoint-srcs.hujson", - "full_policy": { - "ssh": [{ - "action": "accept", - "dst": ["tag:server"], - "src": ["thor@example.org", "freya@example.com"], - "users": ["root"] - }], - "sshTests": [ - {"accept": ["root"], "dst": ["tag:server"], "src": "thor@example.org"}, - {"accept": ["root"], "dst": ["tag:server"], "src": "freya@example.com"} - ], - "tagOwners": {"tag:prod": ["odin@example.com"], "tag:server": ["odin@example.com"]} - } - }, - "topology": {"users": [ - {"id": 1, "name": "odin", "email": "odin@example.com"}, - {"id": 2, "name": "thor", "email": "thor@example.org"}, - {"id": 3, "name": "freya", "email": "freya@example.com"} - ], "nodes": {"beedrill": { - "hostname": "beedrill", - "tags": ["tag:server"], - "ipv4": "100.64.0.16", - "ipv6": "fd7a:115c:a1e0::10", - "routable_ips": [], - "approved_routes": [] - }, "blastoise": { - "hostname": "blastoise", - "tags": ["tag:exit", "tag:router"], - "ipv4": "100.64.0.9", - "ipv6": "fd7a:115c:a1e0::9", - "routable_ips": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "bulbasaur": { - "hostname": "bulbasaur", - "tags": [], - "ipv4": "100.64.0.19", - "ipv6": "fd7a:115c:a1e0::13", - "user": "odin", - "routable_ips": [], - "approved_routes": [] - }, "charmander": { - "hostname": "charmander", - "tags": ["tag:exit"], - "ipv4": "100.64.0.4", - "ipv6": "fd7a:115c:a1e0::4", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "fearow": { - "hostname": "fearow", - "tags": ["tag:fearow"], - "ipv4": "100.64.0.12", - "ipv6": "fd7a:115c:a1e0::c", - "routable_ips": ["10.55.0.0/16"], - "approved_routes": [] - }, "ivysaur": { - "hostname": "ivysaur", - "tags": [], - "ipv4": "100.64.0.17", - "ipv6": "fd7a:115c:a1e0::11", - "user": "thor", - "routable_ips": [], - "approved_routes": [] - }, "kakuna": { - "hostname": "kakuna", - "tags": ["tag:prod"], - "ipv4": "100.64.0.15", - "ipv6": "fd7a:115c:a1e0::f", - "routable_ips": [], - "approved_routes": [] - }, "pidgeotto": { - "hostname": "pidgeotto", - "tags": ["tag:pidgeotto"], - "ipv4": "100.64.0.3", - "ipv6": "fd7a:115c:a1e0::3", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "pidgey": { - "hostname": "pidgey", - "tags": ["tag:pidgey"], - "ipv4": "100.64.0.2", - "ipv6": "fd7a:115c:a1e0::2", - "routable_ips": ["0.0.0.0/0", "::/0"], - "approved_routes": [] - }, "raticate": { - "hostname": "raticate", - "tags": ["tag:group-b"], - "ipv4": "100.64.0.6", - "ipv6": "fd7a:115c:a1e0::6", - "routable_ips": [], - "approved_routes": [] - }, "rattata": { - "hostname": "rattata", - "tags": ["tag:group-a"], - "ipv4": "100.64.0.5", - "ipv6": "fd7a:115c:a1e0::5", - "routable_ips": [], - "approved_routes": [] - }, "spearow": { - "hostname": "spearow", - "tags": ["tag:spearow"], - "ipv4": "100.64.0.11", - "ipv6": "fd7a:115c:a1e0::b", - "routable_ips": ["10.44.0.0/16"], - "approved_routes": [] - }, "squirtle": { - "hostname": "squirtle", - "tags": ["tag:router"], - "ipv4": "100.64.0.13", - "ipv6": "fd7a:115c:a1e0::d", - "routable_ips": ["10.33.0.0/16"], - "approved_routes": [] - }, "venusaur": { - "hostname": "venusaur", - "tags": [], - "ipv4": "100.64.0.18", - "ipv6": "fd7a:115c:a1e0::12", - "user": "freya", - "routable_ips": [], - "approved_routes": [] - }, "weedle": { - "hostname": "weedle", - "tags": ["tag:client"], - "ipv4": "100.64.0.14", - "ipv6": "fd7a:115c:a1e0::e", - "routable_ips": [], - "approved_routes": [] - }}}, - "captures": {"beedrill": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 542612347419738, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "Hostinfo": {"Hostname": "beedrill", "RequestTags": ["tag:server"], "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Tags": ["tag:server"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "MachineKey": "mkey:53c021b9c9f4ae1ddac8c8b0ed1eeb89230ed461590efac913738d2fe4b8b27b", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": [{"principals": [ - {"nodeIP": "100.64.0.17"}, - {"nodeIP": "100.64.0.18"}, - {"nodeIP": "fd7a:115c:a1e0::11"}, - {"nodeIP": "fd7a:115c:a1e0::12"} - ], "sshUsers": {"root": "root"}, "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - }}]}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "542612347419738": { - "ID": 542612347419738, - "LoginName": "beedrill.tail78f774.ts.net", - "DisplayName": "beedrill" - }} - }, - "ssh_rules": [{"principals": [ - {"nodeIP": "100.64.0.17"}, - {"nodeIP": "100.64.0.18"}, - {"nodeIP": "fd7a:115c:a1e0::11"}, - {"nodeIP": "fd7a:115c:a1e0::12"} - ], "sshUsers": {"root": "root"}, "action": { - "accept": true, - "allowAgentForwarding": true, - "allowLocalPortForwarding": true, - "allowRemotePortForwarding": true - }}] - }, "blastoise": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 8870978364341213, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "Hostinfo": { - "Hostname": "blastoise", - "RoutableIPs": ["10.33.0.0/16", "0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit", "tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:18.431901823Z", - "Tags": ["tag:exit", "tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "MachineKey": "mkey:5582d3623685e671b88d885f5cbf0ab1e842f20bdf49c46a2f5e5f74ca5fd622", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "8870978364341213": { - "ID": 8870978364341213, - "LoginName": "blastoise.tail78f774.ts.net", - "DisplayName": "blastoise" - }} - } - }, "bulbasaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "Hostinfo": {"Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "MachineKey": "mkey:6810090c4baee28c1cf9771bf886489db9687e333abe3d0745cf066e1afd3279", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "charmander": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 7582315319859850, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "Hostinfo": { - "Hostname": "charmander", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:exit"], - "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:16.797992969Z", - "Tags": ["tag:exit"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "MachineKey": "mkey:8bae45747ba9f00d16da593b34958d4e3f41d392cc35dc14b1e24024a0c8790f", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "7582315319859850": { - "ID": 7582315319859850, - "LoginName": "charmander.tail78f774.ts.net", - "DisplayName": "charmander" - }} - } - }, "fearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 3513817186690356, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "Hostinfo": { - "Hostname": "fearow", - "RoutableIPs": ["10.55.0.0/16"], - "RequestTags": ["tag:fearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:19.515350414Z", - "Tags": ["tag:fearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "MachineKey": "mkey:6c8a80da0064177792768618a36de60fcf3409a33bfbc1f55585143d9ae90836", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3513817186690356": { - "ID": 3513817186690356, - "LoginName": "fearow.tail78f774.ts.net", - "DisplayName": "fearow" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "ivysaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "Hostinfo": {"Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "MachineKey": "mkey:0214f7e826f3b562c3c3f5d5147344cfd1efdadf4443a9bb970aebf3d53cb766", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "kakuna": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 147482855567493, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "Hostinfo": {"Hostname": "kakuna", "RequestTags": ["tag:prod"], "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Tags": ["tag:prod"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "MachineKey": "mkey:25c0701e68a8a0843c15fb90195e8b899d65af124ee5379d9dbc32b93af6ba43", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "147482855567493": { - "ID": 147482855567493, - "LoginName": "kakuna.tail78f774.ts.net", - "DisplayName": "kakuna" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "pidgeotto": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 7713901239591924, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "Hostinfo": { - "Hostname": "pidgeotto", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgeotto"], - "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:16.262119848Z", - "Tags": ["tag:pidgeotto"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "MachineKey": "mkey:b140526ebd6d89cb8cb5ebfbdd6a48576e21cffbf7dca32fb05df18771bcf065", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "7713901239591924": { - "ID": 7713901239591924, - "LoginName": "pidgeotto.tail78f774.ts.net", - "DisplayName": "pidgeotto" - }} - } - }, "pidgey": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 8254899363490767, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "Hostinfo": { - "Hostname": "pidgey", - "RoutableIPs": ["0.0.0.0/0", "::/0"], - "RequestTags": ["tag:pidgey"], - "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:15.78345442Z", - "Tags": ["tag:pidgey"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "MachineKey": "mkey:9426ecf7a64e56160a129fa69a6cc19a1f14c2d9ed38e7a9c48f7c20bb392d23", - "Peers": [{ - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "8254899363490767": { - "ID": 8254899363490767, - "LoginName": "pidgey.tail78f774.ts.net", - "DisplayName": "pidgey" - }} - } - }, "raticate": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 6886132612070607, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "Hostinfo": {"Hostname": "raticate", "RequestTags": ["tag:group-b"], "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Tags": ["tag:group-b"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "MachineKey": "mkey:7419ddb62fb67aa49799d3d933e9c94a624e1a2b11b6ae816ccd306088f4066b", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }, "6886132612070607": { - "ID": 6886132612070607, - "LoginName": "raticate.tail78f774.ts.net", - "DisplayName": "raticate" - }} - } - }, "rattata": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 2560487136403379, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "Hostinfo": {"Hostname": "rattata", "RequestTags": ["tag:group-a"], "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Tags": ["tag:group-a"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "MachineKey": "mkey:dd7efaea3eafe9b77e3b6537a3e7e581c85f7bc991a6f8d1ac6e1efb7e9bb514", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2560487136403379": { - "ID": 2560487136403379, - "LoginName": "rattata.tail78f774.ts.net", - "DisplayName": "rattata" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "spearow": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 2978985931168101, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "Hostinfo": { - "Hostname": "spearow", - "RoutableIPs": ["10.44.0.0/16"], - "RequestTags": ["tag:spearow"], - "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:18.982822055Z", - "Tags": ["tag:spearow"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "MachineKey": "mkey:6dc7bfb467fbb267c04f76b003bf523eadbce5132703ccf29fbf2ca8201f8a7f", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2978985931168101": { - "ID": 2978985931168101, - "LoginName": "spearow.tail78f774.ts.net", - "DisplayName": "spearow" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "squirtle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 4350398567006230, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "Hostinfo": { - "Hostname": "squirtle", - "RoutableIPs": ["10.33.0.0/16"], - "RequestTags": ["tag:router"], - "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ] - }, - "Created": "2026-05-13T09:31:20.079613238Z", - "Tags": ["tag:router"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "MachineKey": "mkey:8b073f0f781d7e767b2c3fb29daf767da447e9e91828592b216d910c366a2348", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4350398567006230": { - "ID": 4350398567006230, - "LoginName": "squirtle.tail78f774.ts.net", - "DisplayName": "squirtle" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "venusaur": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "Hostinfo": {"Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-admin": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-admin", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "MachineKey": "mkey:1e50d6fc8b254c2c5905ce9d698843623bc1eb750db4a28667d283188eb94b62", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "weedle", "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Cap": 131, - "Tags": ["tag:client"], - "Online": true, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }, "weedle": { - "packet_filter_rules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "packet_filter_matches": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "netmap": { - "Cached": false, - "SelfNode": { - "ID": 2304635934592830, - "StableID": "n3FjgvsmzJ11CNTRL", - "Name": "weedle.tail78f774.ts.net.", - "User": 2304635934592830, - "Key": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "DiscoKey": "discokey:03ed13a450d2e8ad8127a5cb0dbd3e30cca5d59853fe38298e8e27e6ebb18306", - "Addresses": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "AllowedIPs": ["100.64.0.14/32", "fd7a:115c:a1e0::e/128"], - "Endpoints": [ - "77.164.248.136:38225", - "10.65.0.27:38225", - "172.17.0.1:38225", - "172.18.0.1:38225", - "172.19.0.1:38225", - "172.20.0.1:38225", - "172.21.0.1:38225", - "172.22.0.1:38225", - "172.23.0.1:38225", - "172.24.0.1:38225", - "172.25.0.1:38225", - "172.26.0.1:38225", - "172.27.0.1:38225" - ], - "Hostinfo": {"Hostname": "weedle", "RequestTags": ["tag:client"], "Services": [ - {"Proto": "peerapi4", "Port": 63957}, - {"Proto": "peerapi6", "Port": 63957}, - {"Proto": "peerapi-dns-proxy", "Port": 1} - ]}, - "Created": "2026-05-13T09:31:20.585505115Z", - "Tags": ["tag:client"], - "MachineAuthorized": true, - "CapMap": { - "default-auto-update": [false], - "https://tailscale.com/cap/file-sharing": null, - "https://tailscale.com/cap/is-owner": null, - "https://tailscale.com/cap/ssh": null, - "https://tailscale.com/cap/tailnet-lock": null, - "probe-udp-lifetime": null, - "ssh-behavior-v1": null, - "ssh-env-vars": null, - "store-appc-routes": null, - "tailnet-display-name": ["odin@example.com"] - }, - "ComputedName": "weedle", - "ComputedNameWithHost": "weedle" - }, - "AllCaps": [ - "default-auto-update", - "https://tailscale.com/cap/file-sharing", - "https://tailscale.com/cap/is-owner", - "https://tailscale.com/cap/ssh", - "https://tailscale.com/cap/tailnet-lock", - "probe-udp-lifetime", - "ssh-behavior-v1", - "ssh-env-vars", - "store-appc-routes", - "tailnet-display-name" - ], - "NodeKey": "nodekey:ddd03cabe5a0880bbfd631adbafc26e58abaceef3cbc85200f9556bd5661aa0e", - "MachineKey": "mkey:b8347dfd3bf4a0f71836bfe7134089a0abebdeb262849773f5c0f69cab8cbc12", - "Peers": [{ - "ID": 8254899363490767, - "StableID": "nQqFB6DfT721CNTRL", - "Name": "pidgey.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:4a1194f21632107f9ae87c61cdcd49b5513de5f3bbed26c6ea57b140b1db5701", - "DiscoKey": "discokey:92f7e062531900437d0ba7735f2a3eddddf7806a42f8d480bb5408e23df02a1c", - "Addresses": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "AllowedIPs": ["100.64.0.2/32", "fd7a:115c:a1e0::2/128"], - "Endpoints": [ - "77.164.248.136:33170", - "10.65.0.27:33170", - "172.17.0.1:33170", - "172.18.0.1:33170", - "172.19.0.1:33170", - "172.20.0.1:33170", - "172.21.0.1:33170", - "172.22.0.1:33170", - "172.23.0.1:33170", - "172.24.0.1:33170", - "172.25.0.1:33170", - "172.26.0.1:33170", - "172.27.0.1:33170" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgey", "Services": [ - {"Proto": "peerapi4", "Port": 46590}, - {"Proto": "peerapi6", "Port": 46590} - ]}, - "Created": "2026-05-13T09:31:15.78345442Z", - "Cap": 131, - "Tags": ["tag:pidgey"], - "Online": true, - "ComputedName": "pidgey", - "ComputedNameWithHost": "pidgey" - }, { - "ID": 7713901239591924, - "StableID": "nXJgX67eE321CNTRL", - "Name": "pidgeotto.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:16430c93e36cb955fa5abbe7a66970fd73824f400bc4824e4e2fb05fda584851", - "DiscoKey": "discokey:1f0d417a7eb8e9da3030dcc9df9a40888203abec575f3d9c891a01fe7210bb11", - "Addresses": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "AllowedIPs": ["100.64.0.3/32", "fd7a:115c:a1e0::3/128"], - "Endpoints": [ - "77.164.248.136:33907", - "10.65.0.27:33907", - "172.17.0.1:33907", - "172.18.0.1:33907", - "172.19.0.1:33907", - "172.20.0.1:33907", - "172.21.0.1:33907", - "172.22.0.1:33907", - "172.23.0.1:33907", - "172.24.0.1:33907", - "172.25.0.1:33907", - "172.26.0.1:33907", - "172.27.0.1:33907" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "pidgeotto", "Services": [ - {"Proto": "peerapi4", "Port": 34152}, - {"Proto": "peerapi6", "Port": 34152} - ]}, - "Created": "2026-05-13T09:31:16.262119848Z", - "Cap": 131, - "Tags": ["tag:pidgeotto"], - "Online": true, - "ComputedName": "pidgeotto", - "ComputedNameWithHost": "pidgeotto" - }, { - "ID": 7582315319859850, - "StableID": "nFZ2QqZ3D221CNTRL", - "Name": "charmander.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:247f249080e7fd2fcafec11d46373b29a0f37a197ac2ef59c1435f1d422b8c3c", - "DiscoKey": "discokey:7a7dead37ba78b3b7bd83b67e6be2144e406c5d732fc6c5fbfa9c4bec7b1d763", - "Addresses": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "AllowedIPs": ["100.64.0.4/32", "fd7a:115c:a1e0::4/128"], - "Endpoints": [ - "77.164.248.136:53868", - "10.65.0.27:53868", - "172.17.0.1:53868", - "172.18.0.1:53868", - "172.19.0.1:53868", - "172.20.0.1:53868", - "172.21.0.1:53868", - "172.22.0.1:53868", - "172.23.0.1:53868", - "172.24.0.1:53868", - "172.25.0.1:53868", - "172.26.0.1:53868", - "172.27.0.1:53868" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "charmander", "Services": [ - {"Proto": "peerapi4", "Port": 37067}, - {"Proto": "peerapi6", "Port": 37067} - ]}, - "Created": "2026-05-13T09:31:16.797992969Z", - "Cap": 131, - "Tags": ["tag:exit"], - "Online": true, - "ComputedName": "charmander", - "ComputedNameWithHost": "charmander" - }, { - "ID": 2560487136403379, - "StableID": "nJWXG1fezL11CNTRL", - "Name": "rattata.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:a075406c8fb1cb5f698faac7a1f3ee254f055b9dde17771016caf1829caaed13", - "DiscoKey": "discokey:c43b5c7425b822c56d547a0523ba075755cb2fc9dfaafba2af677d26a91eac38", - "Addresses": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "AllowedIPs": ["100.64.0.5/32", "fd7a:115c:a1e0::5/128"], - "Endpoints": [ - "77.164.248.136:55128", - "10.65.0.27:55128", - "172.17.0.1:55128", - "172.18.0.1:55128", - "172.19.0.1:55128", - "172.20.0.1:55128", - "172.21.0.1:55128", - "172.22.0.1:55128", - "172.23.0.1:55128", - "172.24.0.1:55128", - "172.25.0.1:55128", - "172.26.0.1:55128", - "172.27.0.1:55128" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "rattata", "Services": [ - {"Proto": "peerapi4", "Port": 41053}, - {"Proto": "peerapi6", "Port": 41053} - ]}, - "Created": "2026-05-13T09:31:17.345258924Z", - "Cap": 131, - "Tags": ["tag:group-a"], - "Online": true, - "ComputedName": "rattata", - "ComputedNameWithHost": "rattata" - }, { - "ID": 6886132612070607, - "StableID": "nx7aUZ2kmv11CNTRL", - "Name": "raticate.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:416b01ff172fe185de22cd955bc5263b0635f48d4f3ff4fba027c05a87984a23", - "DiscoKey": "discokey:1d733d12d18720d90ba1fba796e97c15eb4d46c05e86fc950ce119a5e13b1431", - "Addresses": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "AllowedIPs": ["100.64.0.6/32", "fd7a:115c:a1e0::6/128"], - "Endpoints": [ - "77.164.248.136:57519", - "10.65.0.27:57519", - "172.17.0.1:57519", - "172.18.0.1:57519", - "172.19.0.1:57519", - "172.20.0.1:57519", - "172.21.0.1:57519", - "172.22.0.1:57519", - "172.23.0.1:57519", - "172.24.0.1:57519", - "172.25.0.1:57519", - "172.26.0.1:57519", - "172.27.0.1:57519" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "raticate", "Services": [ - {"Proto": "peerapi4", "Port": 61927}, - {"Proto": "peerapi6", "Port": 61927} - ]}, - "Created": "2026-05-13T09:31:17.891339506Z", - "Cap": 131, - "Tags": ["tag:group-b"], - "Online": true, - "ComputedName": "raticate", - "ComputedNameWithHost": "raticate" - }, { - "ID": 8870978364341213, - "StableID": "nCcUpRZgGC21CNTRL", - "Name": "blastoise.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:01be22a4271b5a219941164bbb03a5c6bac56e47a468eb99249bacef1e005e1b", - "DiscoKey": "discokey:7dd974bc9382eda95714cf999a4a7b47342a17969920476e28605c6e23d35418", - "Addresses": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "AllowedIPs": ["100.64.0.9/32", "fd7a:115c:a1e0::9/128"], - "Endpoints": [ - "77.164.248.136:53300", - "10.65.0.27:53300", - "172.17.0.1:53300", - "172.18.0.1:53300", - "172.19.0.1:53300", - "172.20.0.1:53300", - "172.21.0.1:53300", - "172.22.0.1:53300", - "172.23.0.1:53300", - "172.24.0.1:53300", - "172.25.0.1:53300", - "172.26.0.1:53300", - "172.27.0.1:53300" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "blastoise", "Services": [ - {"Proto": "peerapi4", "Port": 60534}, - {"Proto": "peerapi6", "Port": 60534} - ]}, - "Created": "2026-05-13T09:31:18.431901823Z", - "Cap": 131, - "Tags": ["tag:exit", "tag:router"], - "Online": true, - "ComputedName": "blastoise", - "ComputedNameWithHost": "blastoise" - }, { - "ID": 2978985931168101, - "StableID": "n21QgiuBGQ11CNTRL", - "Name": "spearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:956795c1ace148b85e3853ea1448a5a30924e90590d358ad3842c5128c14e54a", - "DiscoKey": "discokey:9f088eba31081936a97b75c0a2632506193f0af3d2cb856ec595399fdcfe755b", - "Addresses": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "AllowedIPs": ["100.64.0.11/32", "fd7a:115c:a1e0::b/128"], - "Endpoints": [ - "77.164.248.136:51397", - "10.65.0.27:51397", - "172.17.0.1:51397", - "172.18.0.1:51397", - "172.19.0.1:51397", - "172.20.0.1:51397", - "172.21.0.1:51397", - "172.22.0.1:51397", - "172.23.0.1:51397", - "172.24.0.1:51397", - "172.25.0.1:51397", - "172.26.0.1:51397", - "172.27.0.1:51397" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "spearow", "Services": [ - {"Proto": "peerapi4", "Port": 36186}, - {"Proto": "peerapi6", "Port": 36186} - ]}, - "Created": "2026-05-13T09:31:18.982822055Z", - "Cap": 131, - "Tags": ["tag:spearow"], - "Online": true, - "ComputedName": "spearow", - "ComputedNameWithHost": "spearow" - }, { - "ID": 3513817186690356, - "StableID": "nBebH72RSU11CNTRL", - "Name": "fearow.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:b0f9ef7377b722eef4a183de8a96300478faaaaad4ba9f3e902d9db2b5e23d50", - "DiscoKey": "discokey:dbd521e7cc5e2b98779dbe4775b2072c536576f5683cc9e8162a15c670feb34c", - "Addresses": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "AllowedIPs": ["100.64.0.12/32", "fd7a:115c:a1e0::c/128"], - "Endpoints": [ - "77.164.248.136:36246", - "10.65.0.27:36246", - "172.17.0.1:36246", - "172.18.0.1:36246", - "172.19.0.1:36246", - "172.20.0.1:36246", - "172.21.0.1:36246", - "172.22.0.1:36246", - "172.23.0.1:36246", - "172.24.0.1:36246", - "172.25.0.1:36246", - "172.26.0.1:36246", - "172.27.0.1:36246" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "fearow", "Services": [ - {"Proto": "peerapi4", "Port": 39161}, - {"Proto": "peerapi6", "Port": 39161} - ]}, - "Created": "2026-05-13T09:31:19.515350414Z", - "Cap": 131, - "Tags": ["tag:fearow"], - "Online": true, - "ComputedName": "fearow", - "ComputedNameWithHost": "fearow" - }, { - "ID": 4350398567006230, - "StableID": "n1xK8RbJya11CNTRL", - "Name": "squirtle.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:47e43fc1bedbc5a00244bd79667ce9198bb13b954a1a0b61da50970657f29b2e", - "DiscoKey": "discokey:7dd40082ad6ea9d3d553235d0fb0435acb20e33aa2d602ff0eafe476bf8f9f5f", - "Addresses": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "AllowedIPs": ["100.64.0.13/32", "fd7a:115c:a1e0::d/128"], - "Endpoints": [ - "77.164.248.136:41071", - "10.65.0.27:41071", - "172.17.0.1:41071", - "172.18.0.1:41071", - "172.19.0.1:41071", - "172.20.0.1:41071", - "172.21.0.1:41071", - "172.22.0.1:41071", - "172.23.0.1:41071", - "172.24.0.1:41071", - "172.25.0.1:41071", - "172.26.0.1:41071", - "172.27.0.1:41071" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "squirtle", "Services": [ - {"Proto": "peerapi4", "Port": 43119}, - {"Proto": "peerapi6", "Port": 43119} - ]}, - "Created": "2026-05-13T09:31:20.079613238Z", - "Cap": 131, - "Tags": ["tag:router"], - "Online": true, - "ComputedName": "squirtle", - "ComputedNameWithHost": "squirtle" - }, { - "ID": 147482855567493, - "StableID": "nxg5vD8o9211CNTRL", - "Name": "kakuna.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:cb936fadd41580d07988f0e3cfd39b2789aa45093490bb15faec78200acd191f", - "DiscoKey": "discokey:7e1c52975ca75e69853153072d371cdfdbd060a0b63604f15b2342549ba28360", - "Addresses": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "AllowedIPs": ["100.64.0.15/32", "fd7a:115c:a1e0::f/128"], - "Endpoints": [ - "77.164.248.136:42585", - "10.65.0.27:42585", - "172.17.0.1:42585", - "172.18.0.1:42585", - "172.19.0.1:42585", - "172.20.0.1:42585", - "172.21.0.1:42585", - "172.22.0.1:42585", - "172.23.0.1:42585", - "172.24.0.1:42585", - "172.25.0.1:42585", - "172.26.0.1:42585", - "172.27.0.1:42585" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "kakuna", "Services": [ - {"Proto": "peerapi4", "Port": 51523}, - {"Proto": "peerapi6", "Port": 51523} - ]}, - "Created": "2026-05-13T09:31:21.132911497Z", - "Cap": 131, - "Tags": ["tag:prod"], - "Online": true, - "ComputedName": "kakuna", - "ComputedNameWithHost": "kakuna" - }, { - "ID": 542612347419738, - "StableID": "nKb44NWkE511CNTRL", - "Name": "beedrill.tail78f774.ts.net.", - "User": 1260082990019555, - "Key": "nodekey:c28f5a3e4f5eb73dd696df9c66c1a16ca7f21f797c806d41b5c47b7ec5bf3865", - "DiscoKey": "discokey:326b1c406d2c9d2d20af98068cd85525941e94c5a5f625ec9e198718474fef63", - "Addresses": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "AllowedIPs": ["100.64.0.16/32", "fd7a:115c:a1e0::10/128"], - "Endpoints": [ - "77.164.248.136:41047", - "10.65.0.27:41047", - "172.17.0.1:41047", - "172.18.0.1:41047", - "172.19.0.1:41047", - "172.20.0.1:41047", - "172.21.0.1:41047", - "172.22.0.1:41047", - "172.23.0.1:41047", - "172.24.0.1:41047", - "172.25.0.1:41047", - "172.26.0.1:41047", - "172.27.0.1:41047" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "beedrill", "Services": [ - {"Proto": "peerapi4", "Port": 50358}, - {"Proto": "peerapi6", "Port": 50358} - ]}, - "Created": "2026-05-13T09:31:21.683118244Z", - "Cap": 131, - "Tags": ["tag:server"], - "Online": true, - "ComputedName": "beedrill", - "ComputedNameWithHost": "beedrill" - }, { - "ID": 5033822017498422, - "StableID": "nZp1BBypJg11CNTRL", - "Name": "ivysaur.tail78f774.ts.net.", - "User": 4538565228176803, - "Key": "nodekey:ad270101c86b4ba8f163650cd703a6a30d1afa0812420fd67d22053428610818", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:10d9dbdd6e5aa94d7ad0dfd0dccec2e1cd5152477d4b86549fe9d78b441aec6a", - "Addresses": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "AllowedIPs": ["100.64.0.17/32", "fd7a:115c:a1e0::11/128"], - "Endpoints": [ - "77.164.248.136:54827", - "10.65.0.27:54827", - "172.17.0.1:54827", - "172.18.0.1:54827", - "172.19.0.1:54827", - "172.20.0.1:54827", - "172.21.0.1:54827", - "172.22.0.1:54827", - "172.23.0.1:54827", - "172.24.0.1:54827", - "172.25.0.1:54827", - "172.26.0.1:54827", - "172.27.0.1:54827" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "ivysaur", "Services": [ - {"Proto": "peerapi4", "Port": 62496}, - {"Proto": "peerapi6", "Port": 62496} - ]}, - "Created": "2026-05-13T09:31:22.241162786Z", - "Cap": 131, - "Online": true, - "ComputedName": "ivysaur", - "ComputedNameWithHost": "ivysaur" - }, { - "ID": 1541088879289822, - "StableID": "n3b14vnx2D11CNTRL", - "Name": "venusaur.tail78f774.ts.net.", - "User": 3982058329734709, - "Key": "nodekey:bf38ae3ffb81086e242916fe5677fa6413c03759c74eca417e8f919d50496d52", - "KeyExpiry": "2026-11-09T09:31:22Z", - "DiscoKey": "discokey:43e9e079f1484f1168947f0d1f887875890d578e9c57292d4adbf393afd63536", - "Addresses": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "AllowedIPs": ["100.64.0.18/32", "fd7a:115c:a1e0::12/128"], - "Endpoints": [ - "77.164.248.136:46940", - "10.65.0.27:46940", - "172.17.0.1:46940", - "172.18.0.1:46940", - "172.19.0.1:46940", - "172.20.0.1:46940", - "172.21.0.1:46940", - "172.22.0.1:46940", - "172.23.0.1:46940", - "172.24.0.1:46940", - "172.25.0.1:46940", - "172.26.0.1:46940", - "172.27.0.1:46940" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "venusaur", "Services": [ - {"Proto": "peerapi4", "Port": 42394}, - {"Proto": "peerapi6", "Port": 42394} - ]}, - "Created": "2026-05-13T09:31:22.772693001Z", - "Cap": 131, - "Online": true, - "ComputedName": "venusaur", - "ComputedNameWithHost": "venusaur" - }, { - "ID": 5041737289897031, - "StableID": "ngy1hatQNg11CNTRL", - "Name": "bulbasaur.tail78f774.ts.net.", - "User": 4156223528223174, - "Key": "nodekey:3bf5463099cdade9a63193475dfb9fc239ebac3df8c5723bdd2e6967663d7369", - "KeyExpiry": "2026-11-09T09:31:23Z", - "DiscoKey": "discokey:055e128b7b9c29b6b7f2f5e8554d6a59e03859db72d0fcf30fd18dd2b8c93915", - "Addresses": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "AllowedIPs": ["100.64.0.19/32", "fd7a:115c:a1e0::13/128"], - "Endpoints": [ - "77.164.248.136:42339", - "10.65.0.27:42339", - "172.17.0.1:42339", - "172.18.0.1:42339", - "172.19.0.1:42339", - "172.20.0.1:42339", - "172.21.0.1:42339", - "172.22.0.1:42339", - "172.23.0.1:42339", - "172.24.0.1:42339", - "172.25.0.1:42339", - "172.26.0.1:42339", - "172.27.0.1:42339" - ], - "HomeDERP": 14, - "Hostinfo": {"OS": "linux", "Hostname": "bulbasaur", "Services": [ - {"Proto": "peerapi4", "Port": 38156}, - {"Proto": "peerapi6", "Port": 38156} - ]}, - "Created": "2026-05-13T09:31:23.312350289Z", - "Cap": 131, - "Online": true, - "ComputedName": "bulbasaur", - "ComputedNameWithHost": "bulbasaur" - }], - "DNS": {"ExitNodeFilteredSet": [".ts.net", ".tailscale.net"]}, - "PacketFilter": [{"IPProto": [6, 17, 1, 58], "Srcs": [ - "100.115.94.0/23", - "100.115.96.0/19", - "100.115.128.0/17", - "100.116.0.0/14", - "100.120.0.0/13", - "100.64.0.0/11", - "100.96.0.0/12", - "100.112.0.0/15", - "100.114.0.0/16", - "100.115.0.0/18", - "100.115.64.0/20", - "100.115.80.0/21", - "100.115.88.0/22", - "fd7a:115c:a1e0::/48" - ], "SrcCaps": null, "Dsts": [ - {"Net": "0.0.0.0/0", "Ports": {"First": 0, "Last": 65535}}, - {"Net": "::/0", "Ports": {"First": 0, "Last": 65535}} - ], "Caps": []}], - "PacketFilterRules": [{"SrcIPs": [ - "100.115.94.0-100.127.255.255", - "100.64.0.0-100.115.91.255", - "fd7a:115c:a1e0::/48" - ], "DstPorts": [{"IP": "*", "Ports": {"First": 0, "Last": 65535}}]}], - "SSHPolicy": {"rules": []}, - "CollectServices": false, - "DERPMap": {"Regions": {"1": { - "RegionID": 1, - "RegionCode": "nyc", - "RegionName": "New York City", - "Latitude": 40.7128, - "Longitude": -74.006, - "Nodes": [{ - "Name": "1h", - "RegionID": 1, - "HostName": "derp1h.tailscale.com", - "IPv4": "199.38.181.93", - "IPv6": "2607:f740:f::afd", - "CanPort80": true - }, { - "Name": "1g", - "RegionID": 1, - "HostName": "derp1g.tailscale.com", - "IPv4": "209.177.145.120", - "IPv6": "2607:f740:f::3eb", - "CanPort80": true - }, { - "Name": "1i", - "RegionID": 1, - "HostName": "derp1i.tailscale.com", - "IPv4": "199.38.181.103", - "IPv6": "2607:f740:f::e19", - "CanPort80": true - }, { - "Name": "1f", - "RegionID": 1, - "HostName": "derp1f.tailscale.com", - "IPv4": "199.38.181.104", - "IPv6": "2607:f740:f::bc", - "CanPort80": true - }] - }, "10": { - "RegionID": 10, - "RegionCode": "sea", - "RegionName": "Seattle", - "Latitude": 47.609722, - "Longitude": -122.333056, - "Nodes": [{ - "Name": "10c", - "RegionID": 10, - "HostName": "derp10c.tailscale.com", - "IPv4": "192.73.240.121", - "IPv6": "2607:f740:14::40c", - "CanPort80": true - }, { - "Name": "10d", - "RegionID": 10, - "HostName": "derp10d.tailscale.com", - "IPv4": "192.73.240.132", - "IPv6": "2607:f740:14::500", - "CanPort80": true - }, { - "Name": "10b", - "RegionID": 10, - "HostName": "derp10b.tailscale.com", - "IPv4": "192.73.240.161", - "IPv6": "2607:f740:14::61c", - "CanPort80": true - }] - }, "11": { - "RegionID": 11, - "RegionCode": "sao", - "RegionName": "São Paulo", - "Latitude": -23.55, - "Longitude": -46.633333, - "Nodes": [{ - "Name": "11f", - "RegionID": 11, - "HostName": "derp11f.tailscale.com", - "IPv4": "172.237.61.197", - "IPv6": "2600:3c0d::2000:3bff:fe44:6166", - "CanPort80": true - }, { - "Name": "11g", - "RegionID": 11, - "HostName": "derp11g.tailscale.com", - "IPv4": "172.237.61.190", - "IPv6": "2600:3c0d::2000:62ff:febe:2e67", - "CanPort80": true - }, { - "Name": "11e", - "RegionID": 11, - "HostName": "derp11e.tailscale.com", - "IPv4": "172.237.61.194", - "IPv6": "2600:3c0d::2000:d2ff:fe43:1790", - "CanPort80": true - }] - }, "12": { - "RegionID": 12, - "RegionCode": "ord", - "RegionName": "Chicago", - "Latitude": 41.881944, - "Longitude": -87.627778, - "Nodes": [{ - "Name": "12e", - "RegionID": 12, - "HostName": "derp12e.tailscale.com", - "IPv4": "209.177.158.15", - "IPv6": "2607:f740:e::b17", - "CanPort80": true - }, { - "Name": "12f", - "RegionID": 12, - "HostName": "derp12f.tailscale.com", - "IPv4": "199.38.182.118", - "IPv6": "2607:f740:e::4c8", - "CanPort80": true - }, { - "Name": "12d", - "RegionID": 12, - "HostName": "derp12d.tailscale.com", - "IPv4": "209.177.158.246", - "IPv6": "2607:f740:e::811", - "CanPort80": true - }] - }, "13": { - "RegionID": 13, - "RegionCode": "den", - "RegionName": "Denver", - "Latitude": 39.7392, - "Longitude": -104.9849, - "Nodes": [{ - "Name": "13c", - "RegionID": 13, - "HostName": "derp13c.tailscale.com", - "IPv4": "192.73.242.28", - "IPv6": "2607:f740:16::5c", - "CanPort80": true - }, { - "Name": "13d", - "RegionID": 13, - "HostName": "derp13d.tailscale.com", - "IPv4": "192.73.242.204", - "IPv6": "2607:f740:16::c23", - "CanPort80": true - }, { - "Name": "13b", - "RegionID": 13, - "HostName": "derp13b.tailscale.com", - "IPv4": "192.73.242.187", - "IPv6": "2607:f740:16::640", - "CanPort80": true - }] - }, "14": { - "RegionID": 14, - "RegionCode": "ams", - "RegionName": "Amsterdam", - "Latitude": 52.372778, - "Longitude": 4.893611, - "Nodes": [{ - "Name": "14c", - "RegionID": 14, - "HostName": "derp14c.tailscale.com", - "IPv4": "176.58.93.147", - "IPv6": "2a00:dd80:3c::b09", - "CanPort80": true - }, { - "Name": "14d", - "RegionID": 14, - "HostName": "derp14d.tailscale.com", - "IPv4": "176.58.93.154", - "IPv6": "2a00:dd80:3c::3d5", - "CanPort80": true - }, { - "Name": "14b", - "RegionID": 14, - "HostName": "derp14b.tailscale.com", - "IPv4": "176.58.93.248", - "IPv6": "2a00:dd80:3c::807", - "CanPort80": true - }] - }, "15": { - "RegionID": 15, - "RegionCode": "jnb", - "RegionName": "Johannesburg", - "Latitude": -26.204444, - "Longitude": 28.045556, - "Nodes": [{ - "Name": "15c", - "RegionID": 15, - "HostName": "derp15c.tailscale.com", - "IPv4": "102.67.165.185", - "IPv6": "2c0f:edb0:0:10::b59", - "CanPort80": true - }, { - "Name": "15d", - "RegionID": 15, - "HostName": "derp15d.tailscale.com", - "IPv4": "102.67.165.36", - "IPv6": "2c0f:edb0:0:10::599", - "CanPort80": true - }, { - "Name": "15b", - "RegionID": 15, - "HostName": "derp15b.tailscale.com", - "IPv4": "102.67.165.90", - "IPv6": "2c0f:edb0:0:10::963", - "CanPort80": true - }] - }, "16": { - "RegionID": 16, - "RegionCode": "mia", - "RegionName": "Miami", - "Latitude": 25.78, - "Longitude": -80.21, - "Nodes": [{ - "Name": "16c", - "RegionID": 16, - "HostName": "derp16c.tailscale.com", - "IPv4": "192.73.243.229", - "IPv6": "2607:f740:17::4e4", - "CanPort80": true - }, { - "Name": "16d", - "RegionID": 16, - "HostName": "derp16d.tailscale.com", - "IPv4": "192.73.243.141", - "IPv6": "2607:f740:17::475", - "CanPort80": true - }, { - "Name": "16b", - "RegionID": 16, - "HostName": "derp16b.tailscale.com", - "IPv4": "192.73.243.135", - "IPv6": "2607:f740:17::476", - "CanPort80": true - }] - }, "17": { - "RegionID": 17, - "RegionCode": "lax", - "RegionName": "Los Angeles", - "Latitude": 34.05, - "Longitude": -118.25, - "Nodes": [{ - "Name": "17c", - "RegionID": 17, - "HostName": "derp17c.tailscale.com", - "IPv4": "208.111.40.12", - "IPv6": "2607:f740:c::10", - "CanPort80": true - }, { - "Name": "17d", - "RegionID": 17, - "HostName": "derp17d.tailscale.com", - "IPv4": "208.111.40.216", - "IPv6": "2607:f740:c::e1b", - "CanPort80": true - }, { - "Name": "17b", - "RegionID": 17, - "HostName": "derp17b.tailscale.com", - "IPv4": "192.73.244.245", - "IPv6": "2607:f740:c::646", - "CanPort80": true - }] - }, "18": { - "RegionID": 18, - "RegionCode": "par", - "RegionName": "Paris", - "Latitude": 48.856667, - "Longitude": 2.352222, - "Nodes": [{ - "Name": "18c", - "RegionID": 18, - "HostName": "derp18c.tailscale.com", - "IPv4": "176.58.90.207", - "IPv6": "2a00:dd80:3e::c19", - "CanPort80": true - }, { - "Name": "18d", - "RegionID": 18, - "HostName": "derp18d.tailscale.com", - "IPv4": "176.58.90.104", - "IPv6": "2a00:dd80:3e::f2e", - "CanPort80": true - }, { - "Name": "18b", - "RegionID": 18, - "HostName": "derp18b.tailscale.com", - "IPv4": "176.58.90.147", - "IPv6": "2a00:dd80:3e::363", - "CanPort80": true - }] - }, "19": { - "RegionID": 19, - "RegionCode": "mad", - "RegionName": "Madrid", - "Latitude": 40.416944, - "Longitude": -3.703333, - "Nodes": [{ - "Name": "19c", - "RegionID": 19, - "HostName": "derp19c.tailscale.com", - "IPv4": "45.159.97.61", - "IPv6": "2a00:dd80:14:10::20", - "CanPort80": true - }, { - "Name": "19d", - "RegionID": 19, - "HostName": "derp19d.tailscale.com", - "IPv4": "45.159.97.233", - "IPv6": "2a00:dd80:14:10::34a", - "CanPort80": true - }, { - "Name": "19b", - "RegionID": 19, - "HostName": "derp19b.tailscale.com", - "IPv4": "45.159.97.144", - "IPv6": "2a00:dd80:14:10::335", - "CanPort80": true - }] - }, "2": { - "RegionID": 2, - "RegionCode": "sfo", - "RegionName": "San Francisco", - "Latitude": 37.7775, - "Longitude": -122.416389, - "Nodes": [{ - "Name": "2e", - "RegionID": 2, - "HostName": "derp2e.tailscale.com", - "IPv4": "192.73.252.134", - "IPv6": "2607:f740:0:3f::44c", - "CanPort80": true - }, { - "Name": "2f", - "RegionID": 2, - "HostName": "derp2f.tailscale.com", - "IPv4": "208.111.34.178", - "IPv6": "2607:f740:0:3f::f4", - "CanPort80": true - }, { - "Name": "2d", - "RegionID": 2, - "HostName": "derp2d.tailscale.com", - "IPv4": "192.73.252.65", - "IPv6": "2607:f740:0:3f::287", - "CanPort80": true - }] - }, "20": { - "RegionID": 20, - "RegionCode": "hkg", - "RegionName": "Hong Kong", - "Latitude": 22.3193, - "Longitude": 114.1694, - "Nodes": [{ - "Name": "20c", - "RegionID": 20, - "HostName": "derp20c.tailscale.com", - "IPv4": "205.147.105.30", - "IPv6": "2403:2500:8000:1::5fb", - "CanPort80": true - }, { - "Name": "20d", - "RegionID": 20, - "HostName": "derp20d.tailscale.com", - "IPv4": "205.147.105.78", - "IPv6": "2403:2500:8000:1::e9a", - "CanPort80": true - }, { - "Name": "20b", - "RegionID": 20, - "HostName": "derp20b.tailscale.com", - "IPv4": "103.6.84.152", - "IPv6": "2403:2500:8000:1::ef6", - "CanPort80": true - }] - }, "21": { - "RegionID": 21, - "RegionCode": "tor", - "RegionName": "Toronto", - "Latitude": 43.741667, - "Longitude": -79.373333, - "Nodes": [{ - "Name": "21c", - "RegionID": 21, - "HostName": "derp21c.tailscale.com", - "IPv4": "162.248.221.215", - "IPv6": "2607:f740:50::f10", - "CanPort80": true - }, { - "Name": "21d", - "RegionID": 21, - "HostName": "derp21d.tailscale.com", - "IPv4": "162.248.221.248", - "IPv6": "2607:f740:50::ca4", - "CanPort80": true - }, { - "Name": "21b", - "RegionID": 21, - "HostName": "derp21b.tailscale.com", - "IPv4": "162.248.221.199", - "IPv6": "2607:f740:50::1d1", - "CanPort80": true - }] - }, "22": { - "RegionID": 22, - "RegionCode": "waw", - "RegionName": "Warsaw", - "Latitude": 52.23, - "Longitude": 21.011111, - "Nodes": [{ - "Name": "22c", - "RegionID": 22, - "HostName": "derp22c.tailscale.com", - "IPv4": "45.159.98.253", - "IPv6": "2a00:dd80:40:100::3f", - "CanPort80": true - }, { - "Name": "22d", - "RegionID": 22, - "HostName": "derp22d.tailscale.com", - "IPv4": "45.159.98.145", - "IPv6": "2a00:dd80:40:100::211", - "CanPort80": true - }, { - "Name": "22b", - "RegionID": 22, - "HostName": "derp22b.tailscale.com", - "IPv4": "45.159.98.196", - "IPv6": "2a00:dd80:40:100::316", - "CanPort80": true - }] - }, "23": { - "RegionID": 23, - "RegionCode": "dbi", - "RegionName": "Dubai", - "Latitude": 25.263056, - "Longitude": 55.297222, - "Nodes": [{ - "Name": "23c", - "RegionID": 23, - "HostName": "derp23c.tailscale.com", - "IPv4": "185.34.3.207", - "IPv6": "2a00:dd80:3f:100::a50", - "CanPort80": true - }, { - "Name": "23d", - "RegionID": 23, - "HostName": "derp23d.tailscale.com", - "IPv4": "185.34.3.75", - "IPv6": "2a00:dd80:3f:100::97e", - "CanPort80": true - }, { - "Name": "23b", - "RegionID": 23, - "HostName": "derp23b.tailscale.com", - "IPv4": "185.34.3.232", - "IPv6": "2a00:dd80:3f:100::76f", - "CanPort80": true - }] - }, "24": { - "RegionID": 24, - "RegionCode": "hnl", - "RegionName": "Honolulu", - "Latitude": 21.306944, - "Longitude": -157.858333, - "Nodes": [{ - "Name": "24c", - "RegionID": 24, - "HostName": "derp24c.tailscale.com", - "IPv4": "208.83.233.233", - "IPv6": "2001:19f0:c000:c591:5400:04ff:fe26:2c5f", - "CanPort80": true - }, { - "Name": "24d", - "RegionID": 24, - "HostName": "derp24d.tailscale.com", - "IPv4": "208.72.155.133", - "IPv6": "2001:19f0:c000:c564:5400:04ff:fe26:2ba8", - "CanPort80": true - }, { - "Name": "24b", - "RegionID": 24, - "HostName": "derp24b.tailscale.com", - "IPv4": "208.83.234.151", - "IPv6": "2001:19f0:c000:c586:5400:04ff:fe26:2ba6", - "CanPort80": true - }] - }, "25": { - "RegionID": 25, - "RegionCode": "nai", - "RegionName": "Nairobi", - "Latitude": -1.286389, - "Longitude": 36.817222, - "Nodes": [{ - "Name": "25c", - "RegionID": 25, - "HostName": "derp25c.tailscale.com", - "IPv4": "102.67.167.37", - "IPv6": "2c0f:edb0:2000:1::2c7", - "CanPort80": true - }, { - "Name": "25d", - "RegionID": 25, - "HostName": "derp25d.tailscale.com", - "IPv4": "102.67.167.188", - "IPv6": "2c0f:edb0:2000:1::188", - "CanPort80": true - }, { - "Name": "25b", - "RegionID": 25, - "HostName": "derp25b.tailscale.com", - "IPv4": "102.67.167.245", - "IPv6": "2c0f:edb0:2000:1::2e9", - "CanPort80": true - }] - }, "26": { - "RegionID": 26, - "RegionCode": "nue", - "RegionName": "Nuremberg", - "Latitude": 49.453889, - "Longitude": 11.0775, - "Nodes": [{ - "Name": "26c", - "RegionID": 26, - "HostName": "derp26c.tailscale.com", - "IPv4": "49.12.193.137", - "IPv6": "2a01:4f8:1c1c:5c70::1", - "CanPort80": true - }, { - "Name": "26d", - "RegionID": 26, - "HostName": "derp26d.tailscale.com", - "IPv4": "49.13.204.141", - "IPv6": "2a01:4f8:1c0c:7d06::1", - "CanPort80": true - }, { - "Name": "26b", - "RegionID": 26, - "HostName": "derp26b.tailscale.com", - "IPv4": "167.235.72.200", - "IPv6": "2a01:4f8:1c1c:47b6::1", - "CanPort80": true - }] - }, "27": { - "RegionID": 27, - "RegionCode": "iad", - "RegionName": "Ashburn", - "Latitude": 39.03, - "Longitude": -77.471111, - "Nodes": [{ - "Name": "27d", - "RegionID": 27, - "HostName": "derp27d.tailscale.com", - "IPv4": "178.156.152.106", - "IPv6": "2a01:4ff:f0:3c8e::1", - "CanPort80": true - }, { - "Name": "27c", - "RegionID": 27, - "HostName": "derp27c.tailscale.com", - "IPv4": "178.156.152.91", - "IPv6": "2a01:4ff:f0:3913::1", - "CanPort80": true - }, { - "Name": "27e", - "RegionID": 27, - "HostName": "derp27e.tailscale.com", - "IPv4": "178.156.134.232", - "IPv6": "2a01:4ff:f0:28d4::1", - "CanPort80": true - }, { - "Name": "27b", - "RegionID": 27, - "HostName": "derp27b.tailscale.com", - "IPv4": "5.161.218.233", - "IPv6": "2a01:4ff:f0:3db9::1", - "CanPort80": true - }] - }, "28": { - "RegionID": 28, - "RegionCode": "hel", - "RegionName": "Helsinki", - "Latitude": 60.170833, - "Longitude": 24.9375, - "Nodes": [{ - "Name": "28c", - "RegionID": 28, - "HostName": "derp28c.tailscale.com", - "IPv4": "95.217.2.165", - "IPv6": "2a01:4f9:c012:cd74::1", - "CanPort80": true - }, { - "Name": "28d", - "RegionID": 28, - "HostName": "derp28d.tailscale.com", - "IPv4": "157.180.28.32", - "IPv6": "2a01:4f9:c012:2e5b::1", - "CanPort80": true - }, { - "Name": "28b", - "RegionID": 28, - "HostName": "derp28b.tailscale.com", - "IPv4": "65.109.143.62", - "IPv6": "2a01:4f9:c012:d55c::1", - "CanPort80": true - }] - }, "3": { - "RegionID": 3, - "RegionCode": "sin", - "RegionName": "Singapore", - "Latitude": 1.3521, - "Longitude": 103.8198, - "Nodes": [{ - "Name": "3g", - "RegionID": 3, - "HostName": "derp3g.tailscale.com", - "IPv4": "172.237.72.79", - "IPv6": "2600:3c15::2000:adff:fe08:6fab", - "CanPort80": true - }, { - "Name": "3f", - "RegionID": 3, - "HostName": "derp3f.tailscale.com", - "IPv4": "172.237.72.8", - "IPv6": "2600:3c15::2000:53ff:fe48:a668", - "CanPort80": true - }, { - "Name": "3h", - "RegionID": 3, - "HostName": "derp3h.tailscale.com", - "IPv4": "172.237.66.30", - "IPv6": "2600:3c15::2000:3dff:fe44:50aa", - "CanPort80": true - }, { - "Name": "3e", - "RegionID": 3, - "HostName": "derp3e.tailscale.com", - "IPv4": "172.237.72.43", - "IPv6": "2600:3c15::2000:6cff:fee4:d799", - "CanPort80": true - }] - }, "4": { - "RegionID": 4, - "RegionCode": "fra", - "RegionName": "Frankfurt", - "Latitude": 50.1109, - "Longitude": 8.6821, - "Nodes": [{ - "Name": "4h", - "RegionID": 4, - "HostName": "derp4h.tailscale.com", - "IPv4": "185.40.234.77", - "IPv6": "2a00:dd80:20::bcf", - "CanPort80": true - }, { - "Name": "4i", - "RegionID": 4, - "HostName": "derp4i.tailscale.com", - "IPv4": "185.40.234.53", - "IPv6": "2a00:dd80:20::8a6", - "CanPort80": true - }, { - "Name": "4g", - "RegionID": 4, - "HostName": "derp4g.tailscale.com", - "IPv4": "185.40.234.113", - "IPv6": "2a00:dd80:20::8f", - "CanPort80": true - }, { - "Name": "4j", - "RegionID": 4, - "HostName": "derp4j.tailscale.com", - "IPv4": "185.40.234.176", - "IPv6": "2a00:dd80:20::e67", - "CanPort80": true - }, { - "Name": "4f", - "RegionID": 4, - "HostName": "derp4f.tailscale.com", - "IPv4": "185.40.234.219", - "IPv6": "2a00:dd80:20::a25", - "CanPort80": true - }] - }, "5": { - "RegionID": 5, - "RegionCode": "syd", - "RegionName": "Sydney", - "Latitude": -33.867778, - "Longitude": 151.21, - "Nodes": [{ - "Name": "5f", - "RegionID": 5, - "HostName": "derp5f.tailscale.com", - "IPv4": "172.105.166.103", - "IPv6": "2400:8907::2000:ccff:fe1f:80da", - "CanPort80": true - }, { - "Name": "5g", - "RegionID": 5, - "HostName": "derp5g.tailscale.com", - "IPv4": "172.105.169.57", - "IPv6": "2400:8907::2000:2fff:fea7:57f4", - "CanPort80": true - }, { - "Name": "5e", - "RegionID": 5, - "HostName": "derp5e.tailscale.com", - "IPv4": "172.105.179.230", - "IPv6": "2400:8907::2000:ceff:fe8d:4f4e", - "CanPort80": true - }] - }, "6": { - "RegionID": 6, - "RegionCode": "blr", - "RegionName": "Bengaluru", - "Latitude": 12.9716, - "Longitude": 77.5946, - "Nodes": [{ - "Name": "6a", - "RegionID": 6, - "HostName": "derp6.tailscale.com", - "IPv4": "68.183.90.120", - "IPv6": "2400:6180:100:d0::982:d001", - "CanPort80": true - }] - }, "7": { - "RegionID": 7, - "RegionCode": "tok", - "RegionName": "Tokyo", - "Latitude": 35.6764, - "Longitude": 139.65, - "Nodes": [{ - "Name": "7g", - "RegionID": 7, - "HostName": "derp7g.tailscale.com", - "IPv4": "172.238.6.179", - "IPv6": "2600:3c18::2000:3fff:fe80:3ebd", - "CanPort80": true - }, { - "Name": "7f", - "RegionID": 7, - "HostName": "derp7f.tailscale.com", - "IPv4": "172.238.6.34", - "IPv6": "2600:3c18::2000:acff:fe8e:3ed5", - "CanPort80": true - }, { - "Name": "7h", - "RegionID": 7, - "HostName": "derp7h.tailscale.com", - "IPv4": "172.237.28.183", - "IPv6": "2600:3c18::2000:b1ff:fea9:4560", - "CanPort80": true - }, { - "Name": "7e", - "RegionID": 7, - "HostName": "derp7e.tailscale.com", - "IPv4": "172.238.6.180", - "IPv6": "2600:3c18::2000:60ff:fe0f:6e83", - "STUNOnly": true, - "CanPort80": true - }] - }, "8": { - "RegionID": 8, - "RegionCode": "lhr", - "RegionName": "London", - "Latitude": 51.5072, - "Longitude": 0.1276, - "Nodes": [{ - "Name": "8f", - "RegionID": 8, - "HostName": "derp8f.tailscale.com", - "IPv4": "176.58.88.183", - "IPv6": "2a00:dd80:3a::dfa", - "CanPort80": true - }, { - "Name": "8g", - "RegionID": 8, - "HostName": "derp8g.tailscale.com", - "IPv4": "176.58.92.254", - "IPv6": "2a00:dd80:3a::ed", - "CanPort80": true - }, { - "Name": "8e", - "RegionID": 8, - "HostName": "derp8e.tailscale.com", - "IPv4": "176.58.92.144", - "IPv6": "2a00:dd80:3a::b33", - "CanPort80": true - }] - }, "9": { - "RegionID": 9, - "RegionCode": "dfw", - "RegionName": "Dallas", - "Latitude": 32.779167, - "Longitude": -96.808889, - "Nodes": [{ - "Name": "9e", - "RegionID": 9, - "HostName": "derp9e.tailscale.com", - "IPv4": "192.73.248.83", - "IPv6": "2607:f740:100::359", - "CanPort80": true - }, { - "Name": "9f", - "RegionID": 9, - "HostName": "derp9f.tailscale.com", - "IPv4": "209.177.156.197", - "IPv6": "2607:f740:100::cad", - "CanPort80": true - }, { - "Name": "9d", - "RegionID": 9, - "HostName": "derp9d.tailscale.com", - "IPv4": "209.177.156.94", - "IPv6": "2607:f740:100::c05", - "CanPort80": true - }] - }}}, - "DisplayMessages": null, - "TKAEnabled": false, - "TKAHead": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Domain": "odin@example.com", - "DomainAuditLogID": "", - "UserProfiles": {"1260082990019555": { - "ID": 1260082990019555, - "LoginName": "tagged-devices", - "DisplayName": "Tagged Devices" - }, "2304635934592830": { - "ID": 2304635934592830, - "LoginName": "weedle.tail78f774.ts.net", - "DisplayName": "weedle" - }, "3982058329734709": { - "ID": 3982058329734709, - "LoginName": "freya@example.com", - "DisplayName": "freya" - }, "4156223528223174": { - "ID": 4156223528223174, - "LoginName": "odin@example.com", - "DisplayName": "odin" - }, "4538565228176803": { - "ID": 4538565228176803, - "LoginName": "thor@example.org", - "DisplayName": "thor" - }} - } - }} -} diff --git a/hscontrol/policy/v2/types.go b/hscontrol/policy/v2/types.go index 50a89f8c1..da088b59e 100644 --- a/hscontrol/policy/v2/types.go +++ b/hscontrol/policy/v2/types.go @@ -20,7 +20,6 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/views" "tailscale.com/util/multierr" - "tailscale.com/util/set" "tailscale.com/util/slicesx" ) @@ -63,7 +62,7 @@ var ( // SaaS imposes no minimum (0s is accepted) so headscale matches. const ( SSHCheckPeriodDefault = 12 * time.Hour - SSHCheckPeriodMax = 7 * 24 * time.Hour + SSHCheckPeriodMax = 168 * time.Hour ) // ACL validation errors. @@ -96,7 +95,7 @@ var ( // nodeAttrUnsupportedCaps lists caps that headscale parses but cannot act on // today. Each entry maps to the tracking issue an operator can follow. The // caps are accepted by Tailscale SaaS, but delivering them via headscale -// without the matching server-side machinery would be misleading — nodes +// without the matching server-side machinery would be misleading -- nodes // would advertise a feature that does not work. Reject at policy load and // point operators at the issue. var nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{ @@ -105,6 +104,9 @@ var nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{ // Policy validation errors. var ( + ErrUnknownAliasType = errors.New("unknown alias type") + ErrUnknownAutoApprover = errors.New("unknown auto approver type") + ErrUnknownOwnerType = errors.New("unknown owner type") ErrInvalidUsername = errors.New("username must contain @") ErrUserNotFound = errors.New("user not found") ErrMultipleUsersFound = errors.New("multiple users found") @@ -142,6 +144,8 @@ var ( ErrHostNotDefined = errors.New("host not defined in policy") ErrSSHSourceAliasNotSupported = errors.New("alias not supported for SSH source") ErrSSHDestAliasNotSupported = errors.New("alias not supported for SSH destination") + ErrUnknownSSHDestAlias = errors.New("unknown SSH destination alias type") + ErrUnknownSSHSrcAlias = errors.New("unknown SSH source alias type") ErrUnknownField = errors.New("unknown field") ErrProtocolNoSpecificPorts = errors.New("protocol does not support specific ports") ErrTestEmptyAssertions = errors.New("test entry must have at least one of \"accept\" or \"deny\"") @@ -258,7 +262,26 @@ func (a AliasWithPorts) MarshalJSON() ([]byte, error) { return []byte(`""`), nil } - alias := a.String() + var alias string + + switch v := a.Alias.(type) { + case *Username: + alias = string(*v) + case *Group: + alias = string(*v) + case *Tag: + alias = string(*v) + case *Host: + alias = string(*v) + case *Prefix: + alias = v.String() + case *AutoGroup: + alias = string(*v) + case Asterix: + alias = "*" + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownAliasType, v) + } // If no ports are specified if len(a.Ports) == 0 { @@ -318,15 +341,15 @@ func (a Asterix) resolve(_ *Policy, _ types.Users, _ views.Slice[types.NodeView] // IPSet merges overlapping ranges (e.g. 10.0.0.0/8 absorbs // 10.33.0.0/16), but Tailscale preserves individual route entries. func approvedSubnetRoutes(nodes views.Slice[types.NodeView]) []string { - seen := make(set.Set[string]) + seen := make(map[string]bool) var routes []string for _, node := range nodes.All() { for _, route := range node.SubnetRoutes() { s := route.String() - if !seen.Contains(s) { - seen.Add(s) + if !seen[s] { + seen[s] = true routes = append(routes, s) } } @@ -379,12 +402,11 @@ func (u *Username) CanBeAutoApprover() bool { return true } -// resolveUser attempts to find a user in the provided [types.Users] slice that matches the [Username]. -// It prioritizes matching the [types.User.ProviderIdentifier], and if not found, it falls back to matching -// the [types.User.Email] or [types.User.Name]. +// resolveUser attempts to find a user in the provided [types.Users] slice that matches the Username. +// It prioritizes matching the ProviderIdentifier, and if not found, it falls back to matching the Email or Name. // If no matching user is found, it returns an error indicating no user matching. // If multiple matching users are found, it returns an error indicating multiple users matching. -// It returns the matched [types.User] and a nil error if exactly one match is found. +// It returns the matched types.User and a nil error if exactly one match is found. func (u *Username) resolveUser(users types.Users) (types.User, error) { var potentialUsers types.Users @@ -696,9 +718,9 @@ func (p *Prefix) UnmarshalJSON(b []byte) error { return nil } -// Resolve resolves the [Prefix] to an [netipx.IPSet]. The [netipx.IPSet] will -// contain all the IP addresses that the [Prefix] represents within Headscale. -// It is the product of the [Prefix] and the [Policy], [types.Users], and [types.Nodes]. +// Resolve resolves the Prefix to an IPSet. The IPSet will contain all the IP +// addresses that the Prefix represents within Headscale. It is the product +// of the Prefix and the Policy, Users, and Nodes. // // See [Policy], [types.Users], and [types.Nodes] for more details. func (p *Prefix) Resolve(_ *Policy, _ types.Users, nodes views.Slice[types.NodeView]) (ResolvedAddresses, error) { @@ -842,16 +864,16 @@ type Alias interface { UnmarshalJSON(b []byte) error // String renders the alias back to its policy-file form. Implementations - // are expected to return a value that round-trips through [parseAlias] for + // are expected to return a value that round-trips through parseAlias for // any alias the parser accepted, so callers can use it as a stable // identity in rendered errors and logs. String() string - // Resolve resolves the [Alias] to a [netipx.IPSet]. The [netipx.IPSet] will - // contain all the IP addresses that the [Alias] represents within Headscale. - // It is the product of the [Alias] and the [Policy], [types.Users] and - // [types.Nodes]. This is an interface definition and the implementation is - // independent of the [Alias] type. + // Resolve resolves the Alias to an IPSet. The IPSet will contain all the IP + // addresses that the Alias represents within Headscale. It is the product + // of the Alias and the Policy, Users and Nodes. + // This is an interface definition and the implementation is independent of + // the Alias type. Resolve(pol *Policy, users types.Users, nodes views.Slice[types.NodeView]) (ResolvedAddresses, error) resolve(pol *Policy, users types.Users, nodes views.Slice[types.NodeView]) (*netipx.IPSet, error) @@ -1109,7 +1131,24 @@ func (a *Aliases) MarshalJSON() ([]byte, error) { aliases := make([]string, len(*a)) for i, alias := range *a { - aliases[i] = alias.String() + switch v := alias.(type) { + case *Username: + aliases[i] = string(*v) + case *Group: + aliases[i] = string(*v) + case *Tag: + aliases[i] = string(*v) + case *Host: + aliases[i] = string(*v) + case *Prefix: + aliases[i] = v.String() + case *AutoGroup: + aliases[i] = string(*v) + case Asterix: + aliases[i] = "*" + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownAliasType, v) + } } return json.Marshal(aliases) @@ -1138,7 +1177,7 @@ func buildIPSetMultiErr(ipBuilder *netipx.IPSetBuilder, errs []error) (*netipx.I return ips, multierr.New(append(errs, err)...) } -// Helper function to unmarshal a JSON string into either an [AutoApprover] or [Owner] pointer. +// Helper function to unmarshal a JSON string into either an AutoApprover or Owner pointer. func unmarshalPointer[T any]( b []byte, parseFunc func(string) (T, error), @@ -1186,7 +1225,16 @@ func (aa AutoApprovers) MarshalJSON() ([]byte, error) { approvers := make([]string, len(aa)) for i, approver := range aa { - approvers[i] = approver.String() + switch v := approver.(type) { + case *Username: + approvers[i] = string(*v) + case *Tag: + approvers[i] = string(*v) + case *Group: + approvers[i] = string(*v) + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownAutoApprover, v) + } } return json.Marshal(approvers) @@ -1271,7 +1319,16 @@ func (o Owners) MarshalJSON() ([]byte, error) { owners := make([]string, len(o)) for i, owner := range o { - owners[i] = owner.String() + switch v := owner.(type) { + case *Username: + owners[i] = string(*v) + case *Group: + owners[i] = string(*v) + case *Tag: + owners[i] = string(*v) + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownOwnerType, v) + } } return json.Marshal(owners) @@ -1292,7 +1349,7 @@ func parseOwner(s string) (Owner, error) { type Usernames []Username -// Groups are a map of [Group] to a list of [Username]. +// Groups are a map of Group to a list of Username. type Groups map[Group]Usernames func (g *Groups) Contains(group *Group) error { @@ -1300,15 +1357,17 @@ func (g *Groups) Contains(group *Group) error { return nil } - if _, ok := (*g)[*group]; ok { - return nil + for defined := range map[Group]Usernames(*g) { + if defined == *group { + return nil + } } return fmt.Errorf("%w: %q", ErrGroupNotDefined, group) } -// UnmarshalJSON overrides the default JSON unmarshalling for [Groups] to ensure -// that each group name is validated using the [isGroup] function. This ensures +// UnmarshalJSON overrides the default JSON unmarshalling for Groups to ensure +// that each group name is validated using the isGroup function. This ensures // that all group names conform to the expected format, which is always prefixed // with "group:". If any group name is invalid, an error is returned. func (g *Groups) UnmarshalJSON(b []byte) error { @@ -1464,7 +1523,16 @@ func (to TagOwners) MarshalJSON() ([]byte, error) { ownerStrs := make([]string, len(owners)) for i, owner := range owners { - ownerStrs[i] = owner.String() + switch v := owner.(type) { + case *Username: + ownerStrs[i] = string(*v) + case *Group: + ownerStrs[i] = string(*v) + case *Tag: + ownerStrs[i] = string(*v) + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownOwnerType, v) + } } rawTagOwners[tagStr] = ownerStrs @@ -1473,7 +1541,7 @@ func (to TagOwners) MarshalJSON() ([]byte, error) { return json.Marshal(rawTagOwners) } -// TagOwners are a map of [Tag] to a list of the UserEntities that own the tag. +// TagOwners are a map of Tag to a list of the UserEntities that own the tag. type TagOwners map[Tag]Owners func (to TagOwners) Contains(tagOwner *Tag) error { @@ -1481,8 +1549,10 @@ func (to TagOwners) Contains(tagOwner *Tag) error { return nil } - if _, ok := to[*tagOwner]; ok { - return nil + for defined := range map[Tag]Owners(to) { + if defined == *tagOwner { + return nil + } } return fmt.Errorf("%w: %q", ErrTagNotDefined, tagOwner) @@ -1517,9 +1587,9 @@ func (ap AutoApproverPolicy) MarshalJSON() ([]byte, error) { return json.Marshal(&obj) } -// resolveAutoApprovers resolves the [AutoApprovers] to a map of [netip.Prefix] to [netipx.IPSet]. +// resolveAutoApprovers resolves the AutoApprovers to a map of netip.Prefix to netipx.IPSet. // The resulting map can be used to quickly look up if a node can self-approve a route. -// It is intended for internal use in a [PolicyManager]. +// It is intended for internal use in a PolicyManager. func resolveAutoApprovers(p *Policy, users types.Users, nodes views.Slice[types.NodeView]) (map[netip.Prefix]*netipx.IPSet, *netipx.IPSet, error) { if p == nil { return nil, nil, nil @@ -1583,14 +1653,14 @@ func resolveAutoApprovers(p *Policy, users types.Users, nodes views.Slice[types. return ret, exitNodeSet, nil } -// Action represents the action to take for an [ACL] rule. +// Action represents the action to take for an ACL rule. type Action string const ( ActionAccept Action = "accept" ) -// SSHAction represents the action to take for an [SSH] rule. +// SSHAction represents the action to take for an SSH rule. type SSHAction string const ( @@ -1598,12 +1668,12 @@ const ( SSHActionCheck SSHAction = "check" ) -// String returns the string representation of the [Action]. +// String returns the string representation of the Action. func (a *Action) String() string { return string(*a) } -// UnmarshalJSON implements JSON unmarshaling for [Action]. +// UnmarshalJSON implements JSON unmarshaling for Action. func (a *Action) UnmarshalJSON(b []byte) error { str := strings.Trim(string(b), `"`) switch str { @@ -1616,12 +1686,12 @@ func (a *Action) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements JSON marshaling for [Action]. +// MarshalJSON implements JSON marshaling for Action. func (a *Action) MarshalJSON() ([]byte, error) { return json.Marshal(string(*a)) } -// String returns the string representation of the [SSHAction]. +// String returns the string representation of the SSHAction. func (a *SSHAction) String() string { return string(*a) } @@ -1645,7 +1715,7 @@ func (a *SSHAction) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements JSON marshaling for [SSHAction]. +// MarshalJSON implements JSON marshaling for SSHAction. func (a *SSHAction) MarshalJSON() ([]byte, error) { return json.Marshal(string(*a)) } @@ -1671,13 +1741,51 @@ const ( ProtocolNameWildcard Protocol = "*" ) -// String returns the string representation of the [Protocol]. +// String returns the string representation of the Protocol. func (p *Protocol) String() string { return string(*p) } -// toIANAProtocolNumbers converts a [Protocol] to its IANA protocol numbers. -// Since validation happens during [Protocol.UnmarshalJSON], this method should not fail for valid [Protocol] values. +// Description returns the human-readable description of the Protocol. +func (p *Protocol) Description() string { + switch *p { + case ProtocolNameICMP: + return "Internet Control Message Protocol" + case ProtocolNameIGMP: + return "Internet Group Management Protocol" + case ProtocolNameIPv4: + return "IPv4 encapsulation" + case ProtocolNameTCP: + return "Transmission Control Protocol" + case ProtocolNameEGP: + return "Exterior Gateway Protocol" + case ProtocolNameIGP: + return "Interior Gateway Protocol" + case ProtocolNameUDP: + return "User Datagram Protocol" + case ProtocolNameGRE: + return "Generic Routing Encapsulation" + case ProtocolNameESP: + return "Encapsulating Security Payload" + case ProtocolNameAH: + return "Authentication Header" + case ProtocolNameIPv6ICMP: + return "Internet Control Message Protocol for IPv6" + case ProtocolNameSCTP: + return "Stream Control Transmission Protocol" + case ProtocolNameFC: + return "Fibre Channel" + case ProtocolNameIPInIP: + return "IP-in-IP Encapsulation" + case ProtocolNameWildcard: + return "Wildcard (not supported - use specific protocol)" + default: + return "Unknown Protocol" + } +} + +// toIANAProtocolNumbers converts a Protocol to its IANA protocol numbers. +// Since validation happens during UnmarshalJSON, this method should not fail for valid Protocol values. func (p *Protocol) toIANAProtocolNumbers() []int { switch *p { case "": @@ -1721,13 +1829,13 @@ func (p *Protocol) toIANAProtocolNumbers() []int { } } -// UnmarshalJSON implements JSON unmarshaling for [Protocol]. +// UnmarshalJSON implements JSON unmarshaling for Protocol. // // Tailscale accepts both named ("tcp") and numeric IANA ("6") forms. // Storing whichever form the user wrote leaves downstream code with // two equivalents to handle separately, and any consumer that // branches on the named form would silently mishandle the numeric -// equivalent. Canonicalising to the named form here makes [Protocol] +// equivalent. Canonicalising to the named form here makes Protocol // hold one value post-parse — every downstream consumer sees the // same form regardless of what the user wrote. func (p *Protocol) UnmarshalJSON(b []byte) error { @@ -1751,7 +1859,7 @@ func (p *Protocol) UnmarshalJSON(b []byte) error { return nil } -// validate checks if the [Protocol] is valid. +// validate checks if the Protocol is valid. func (p *Protocol) validate() error { switch *p { case "", ProtocolNameICMP, ProtocolNameIGMP, ProtocolNameIPv4, ProtocolNameIPInIP, @@ -1783,7 +1891,7 @@ func (p *Protocol) validate() error { } } -// MarshalJSON implements JSON marshaling for [Protocol]. +// MarshalJSON implements JSON marshaling for Protocol. func (p *Protocol) MarshalJSON() ([]byte, error) { return json.Marshal(string(*p)) } @@ -1805,7 +1913,7 @@ const ( ProtocolFC = 133 // Fibre Channel ) -// ProtocolNumberToName maps IANA protocol numbers to their [Protocol] name strings. +// ProtocolNumberToName maps IANA protocol numbers to their protocol name strings. var ProtocolNumberToName = map[int]Protocol{ ProtocolICMP: ProtocolNameICMP, ProtocolIGMP: ProtocolNameIGMP, @@ -1829,7 +1937,7 @@ type ACL struct { Destinations []AliasWithPorts `json:"dst"` } -// UnmarshalJSON implements custom unmarshalling for [ACL] that ignores fields starting with '#'. +// UnmarshalJSON implements custom unmarshalling for ACL that ignores fields starting with '#'. // headscale-admin uses # in some field names to add metadata, so we will ignore // those to ensure it doesnt break. // https://github.com/GoodiesHQ/headscale-admin/blob/214a44a9c15c92d2b42383f131b51df10c84017c/src/lib/common/acl.svelte.ts#L38 @@ -1896,7 +2004,7 @@ type NodeAttrGrant struct { IPPool []netip.Prefix `json:"ipPool,omitempty"` } -// aclToGrants converts an [ACL] rule to one or more equivalent [Grant] rules. +// aclToGrants converts an ACL rule to one or more equivalent Grant rules. func aclToGrants(acl ACL) []Grant { ret := make([]Grant, 0, len(acl.Destinations)) @@ -1968,8 +2076,8 @@ func aclToGrants(acl ACL) []Grant { // TODO(kradalby): // Add validation method checking: // All users exists -// All groups and users are valid tag [TagOwners] -// Everything referred to in [ACL]s exists in other +// All groups and users are valid tag TagOwners +// Everything referred to in ACLs exists in other // entities. type Policy struct { // validated is set if the policy has been validated. @@ -1990,7 +2098,7 @@ type Policy struct { RandomizeClientPort bool `json:"randomizeClientPort,omitempty"` } -// MarshalJSON is deliberately not implemented for [Policy]. +// MarshalJSON is deliberately not implemented for Policy. // We use the default JSON marshalling behavior provided by the Go runtime. var ( @@ -2070,7 +2178,7 @@ func validateAutogroupForDst(dst *AutoGroup) error { // autogroup:self / autogroup:internet / autogroup:danger-all are rejected — // none of them describes a stable identity set that a node-level attribute // can attach to. autogroup:admin / autogroup:owner are rejected one layer -// up: [AutoGroup.UnmarshalJSON] returns [ErrInvalidAutogroup] at parse time +// up: AutoGroup.UnmarshalJSON returns ErrInvalidAutogroup at parse time // because those values aren't in the allowed set, so the policy never // reaches this validator. func validateAutogroupForNodeAttrs(ag *AutoGroup) error { @@ -2087,7 +2195,7 @@ func validateAutogroupForNodeAttrs(ag *AutoGroup) error { // validateNodeAttrIPPool rejects ipPool entries outside the CGNAT range or // overlapping the Tailscale-reserved subranges (MagicDNS, Quad100/IPN). A -// [netip.Prefix] is considered "within" CGNAT when it is at least as specific as +// prefix is considered "within" CGNAT when it is at least as specific as // 100.64.0.0/10 and its first address lies inside it. func validateNodeAttrIPPool(prefix netip.Prefix) error { cgnat := tsaddr.CGNATRange() @@ -2140,9 +2248,9 @@ func validateAutogroupForSSHDst(dst *AutoGroup) error { // validateSSHSrcDstCombination validates that SSH source/destination combinations // follow Tailscale's security model: -// - Destination can be: tags, autogroup:self (if source is users/groups), or same-user -// - Tags/autogroup:tagged CANNOT SSH to user destinations -// - [Username] destinations require the source to be that same single user only. +// - Destination can be: tags, autogroup:self (if source is users/groups), or same-user +// - Tags/autogroup:tagged CANNOT SSH to user destinations +// - Username destinations require the source to be that same single user only. func validateSSHSrcDstCombination(sources SSHSrcAliases, destinations SSHDstAliases) error { // Categorize source types srcHasTaggedEntities := false @@ -2195,11 +2303,11 @@ func validateSSHSrcDstCombination(sources SSHSrcAliases, destinations SSHDstAlia return nil } -// validateACLSrcDstCombination validates that [ACL] source/destination combinations +// validateACLSrcDstCombination validates that ACL source/destination combinations // follow Tailscale's security model: -// - autogroup:self destinations require ALL sources to be users, groups, autogroup:member, or wildcard (*) -// - Tags, autogroup:tagged, hosts, and raw IPs are NOT valid sources for autogroup:self -// - Wildcard (*) is allowed because autogroup:self evaluation narrows it per-node to the node's own IPs. +// - autogroup:self destinations require ALL sources to be users, groups, autogroup:member, or wildcard (*) +// - Tags, autogroup:tagged, hosts, and raw IPs are NOT valid sources for autogroup:self +// - Wildcard (*) is allowed because autogroup:self evaluation narrows it per-node to the node's own IPs. func validateACLSrcDstCombination(sources Aliases, destinations []AliasWithPorts) error { // Check if any destination is autogroup:self hasAutogroupSelf := false @@ -2271,17 +2379,13 @@ var tailscaleCapAllowlist = map[tailcfg.PeerCapability]bool{ tailcfg.PeerCapabilityWebUI: true, // tailscale.com/cap/webui tailcfg.PeerCapabilityKubernetes: true, // tailscale.com/cap/kubernetes tailcfg.PeerCapabilityTsIDP: true, // tailscale.com/cap/tsidp - - // tailscale.com/cap/secrets is the capability used by setec - // (github.com/tailscale/setec); allow it so it can be granted via policy. - tailcfg.PeerCapability("tailscale.com/cap/secrets"): true, } -// validateGrantSrcDstCombination validates [Grant]-specific source/destination -// combinations. [Grant]s are stricter than [ACL]s: wildcard (*) sources are NOT +// validateGrantSrcDstCombination validates grant-specific source/destination +// combinations. Grants are stricter than ACLs: wildcard (*) sources are NOT // allowed with autogroup:self destinations because * includes tags, and tags -// cannot use autogroup:self. [ACL]s allow this combination because ACL -// autogroup:self evaluation narrows it per-node, but [Grant]s reject it at +// cannot use autogroup:self. ACLs allow this combination because ACL +// autogroup:self evaluation narrows it per-node, but grants reject it at // validation time. func validateGrantSrcDstCombination(sources Aliases, destinations Aliases) error { hasAutogroupSelf := false @@ -2813,14 +2917,14 @@ func (p *Policy) validate() error { // SSHCheckPeriod represents the check period for SSH "check" mode rules. // nil means not specified (runtime default of 12h applies). -// [SSHCheckPeriod.Always]=true means "always" (check on every request). -// [SSHCheckPeriod.Duration] is an explicit period (min 1m, max 168h). +// Always=true means "always" (check on every request). +// Duration is an explicit period (min 1m, max 168h). type SSHCheckPeriod struct { Always bool Duration time.Duration } -// UnmarshalJSON implements JSON unmarshaling for [SSHCheckPeriod]. +// UnmarshalJSON implements JSON unmarshaling for SSHCheckPeriod. func (p *SSHCheckPeriod) UnmarshalJSON(b []byte) error { str := strings.Trim(string(b), `"`) if str == "always" { @@ -2839,7 +2943,7 @@ func (p *SSHCheckPeriod) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements JSON marshaling for [SSHCheckPeriod]. +// MarshalJSON implements JSON marshaling for SSHCheckPeriod. func (p SSHCheckPeriod) MarshalJSON() ([]byte, error) { if p.Always { return []byte(`"always"`), nil @@ -2876,11 +2980,11 @@ type SSH struct { AcceptEnv []string `json:"acceptEnv,omitempty"` } -// SSHSrcAliases is a list of aliases that can be used as sources in an [SSH] rule. +// SSHSrcAliases is a list of aliases that can be used as sources in an SSH rule. // It can be a list of usernames, groups, tags or autogroups. type SSHSrcAliases []Alias -// MarshalJSON marshals the [Groups] to JSON. +// MarshalJSON marshals the Groups to JSON. func (g *Groups) MarshalJSON() ([]byte, error) { if *g == nil { return []byte("{}"), nil @@ -2945,7 +3049,7 @@ func (a *SSHDstAliases) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON marshals the [SSHDstAliases] to JSON. +// MarshalJSON marshals the SSHDstAliases to JSON. func (a SSHDstAliases) MarshalJSON() ([]byte, error) { if a == nil { return []byte("[]"), nil @@ -2953,15 +3057,28 @@ func (a SSHDstAliases) MarshalJSON() ([]byte, error) { aliases := make([]string, len(a)) for i, alias := range a { - // A wildcard renders as "*" so it gets rejected during unmarshal - // with a proper error message explaining alternatives. - aliases[i] = alias.String() + switch v := alias.(type) { + case *Username: + aliases[i] = string(*v) + case *Tag: + aliases[i] = string(*v) + case *AutoGroup: + aliases[i] = string(*v) + case *Host: + aliases[i] = string(*v) + case Asterix: + // Marshal wildcard as "*" so it gets rejected during unmarshal + // with a proper error message explaining alternatives + aliases[i] = "*" + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownSSHDestAlias, v) + } } return json.Marshal(aliases) } -// MarshalJSON marshals the [SSHSrcAliases] to JSON. +// MarshalJSON marshals the SSHSrcAliases to JSON. func (a *SSHSrcAliases) MarshalJSON() ([]byte, error) { if a == nil || *a == nil { return []byte("[]"), nil @@ -2969,7 +3086,20 @@ func (a *SSHSrcAliases) MarshalJSON() ([]byte, error) { aliases := make([]string, len(*a)) for i, alias := range *a { - aliases[i] = alias.String() + switch v := alias.(type) { + case *Username: + aliases[i] = string(*v) + case *Group: + aliases[i] = string(*v) + case *Tag: + aliases[i] = string(*v) + case *AutoGroup: + aliases[i] = string(*v) + case Asterix: + aliases[i] = "*" + default: + return nil, fmt.Errorf("%w: %T", ErrUnknownSSHSrcAlias, v) + } } return json.Marshal(aliases) @@ -2993,7 +3123,7 @@ func (a *SSHSrcAliases) Resolve(p *Policy, users types.Users, nodes views.Slice[ return newResolvedAddresses(buildIPSetMultiErr(&ips, errs)) } -// SSHDstAliases is a list of aliases that can be used as destinations in an [SSH] rule. +// SSHDstAliases is a list of aliases that can be used as destinations in an SSH rule. // It can be a list of usernames, tags or autogroups. type SSHDstAliases []Alias @@ -3040,20 +3170,20 @@ func (u SSHUsers) LocalpartEntries() []SSHUser { }) } -type SSHUser string //nolint:recvcheck // UnmarshalJSON requires pointer receiver; string-newtype methods use value receivers by convention +type SSHUser string func (u SSHUser) String() string { return string(u) } -// IsLocalpart returns true if the [SSHUser] has the literal `localpart:` +// IsLocalpart returns true if the SSHUser has the literal `localpart:` // prefix. It is a syntactic check only — non-canonical shapes still // pass. func (u SSHUser) IsLocalpart() bool { return strings.HasPrefix(string(u), SSHUserLocalpartPrefix) } -// IsCanonicalLocalpart reports whether the [SSHUser] parses as the +// IsCanonicalLocalpart reports whether the SSHUser parses as the // canonical `localpart:*@` form that resolution acts on. func (u SSHUser) IsCanonicalLocalpart() bool { if !u.IsLocalpart() { @@ -3095,14 +3225,14 @@ func (u SSHUser) ParseLocalpart() (string, error) { return domain, nil } -// MarshalJSON marshals the [SSHUser] to JSON. +// MarshalJSON marshals the SSHUser to JSON. func (u SSHUser) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON trims surrounding whitespace per element. A whitespace- // only entry collapses to `""` and surfaces as `user "" is not valid` in -// the per-rule [Policy.validate] pass. +// the per-rule Validate() pass. func (u *SSHUser) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { //nolint:noinlineerr @@ -3114,7 +3244,7 @@ func (u *SSHUser) UnmarshalJSON(b []byte) error { return nil } -// unmarshalPolicy takes a byte slice and unmarshals it into a [Policy] struct. +// unmarshalPolicy takes a byte slice and unmarshals it into a Policy struct. // In addition to unmarshalling, it will also validate the policy. // This is the only entrypoint of reading a policy from a file or other source. func unmarshalPolicy(b []byte) (*Policy, error) { @@ -3166,8 +3296,8 @@ func unmarshalPolicy(b []byte) (*Policy, error) { return &policy, nil } -// validateProtocolPortCompatibility checks that only TCP, UDP, and SCTP [Protocol]s -// can have specific ports. All other [Protocol]s should only use wildcard ports. +// validateProtocolPortCompatibility checks that only TCP, UDP, and SCTP protocols +// can have specific ports. All other protocols should only use wildcard ports. func validateProtocolPortCompatibility(protocol Protocol, destinations []AliasWithPorts) error { // Only TCP, UDP, and SCTP support specific ports supportsSpecificPorts := protocol == ProtocolNameTCP || protocol == ProtocolNameUDP || protocol == ProtocolNameSCTP || protocol == "" @@ -3193,7 +3323,7 @@ func validateProtocolPortCompatibility(protocol Protocol, destinations []AliasWi // follow: a tests entry describes one connection attempt to one specific // destination port over a connection-oriented protocol and asserts // whether that attempt is allowed or denied. The same shapes remain -// valid inside [ACL] or [Grant] destinations where the rule does not apply. +// valid inside ACL or Grant destinations where the rule does not apply. func validateTests(pol *Policy, tests []PolicyTest) error { var errs []error @@ -3231,16 +3361,15 @@ func validateTests(pol *Policy, tests []PolicyTest) error { return nil } -// validateTestDestination enforces that a tests-block dst describes one -// connection attempt to one specific host on one specific port. SaaS -// rejects three shapes that violate the rule: autogroup:internet (routed -// by exit-node [tailcfg.Node.AllowedIPs], not the packet filter); multi-port -// (range/list/wildcard, no single allow/deny answer); and CIDR ranges -// — both raw `/N` syntax and `hosts:`-table aliases whose RHS is a -// multi-host prefix. Bare IP literals reach this function as *[Prefix] -// /32 or /128 just like explicit `/32` / `/128` does, so the CIDR -// check inspects the raw input string for `/` rather than the parsed -// alias type. +// validateTestDestination rejects tests-block dst shapes that cannot +// describe one connection to one host on one port: +// +// - autogroup:internet (routed by exit-node AllowedIPs), +// - multi-port ranges, lists, or wildcards, +// - CIDR ranges (raw `/N` or hosts: aliases that resolve wider). +// +// Bare IPs parse as /32 or /128 like explicit forms, so the CIDR +// check inspects the raw input rather than the parsed alias type. func validateTestDestination(pol *Policy, dst string) error { awp, err := parseDestinationAlias(dst) if err != nil { diff --git a/hscontrol/policy/v2/types_test.go b/hscontrol/policy/v2/types_test.go index 19134e5b3..1a046f126 100644 --- a/hscontrol/policy/v2/types_test.go +++ b/hscontrol/policy/v2/types_test.go @@ -6244,27 +6244,3 @@ func TestUnmarshalPolicySSHTests(t *testing.T) { }) } } - -func TestValidateCapabilityName(t *testing.T) { - tests := []struct { - name string - cap string - wantErr error - }{ - {name: "custom domain allowed", cap: "example.com/cap/foo", wantErr: nil}, - {name: "allowlisted tailscale cap", cap: "tailscale.com/cap/drive", wantErr: nil}, - {name: "setec secrets cap allowed", cap: "tailscale.com/cap/secrets", wantErr: nil}, - {name: "non-allowlisted tailscale cap rejected", cap: "tailscale.com/cap/nope", wantErr: ErrCapNameTailscaleDomain}, - {name: "url scheme rejected", cap: "https://tailscale.com/cap/drive", wantErr: ErrCapNameInvalidForm}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateCapabilityName(tt.cap) - if tt.wantErr == nil { - require.NoError(t, err) - } else { - require.ErrorIs(t, err, tt.wantErr) - } - }) - } -} diff --git a/hscontrol/policy/v2/utils.go b/hscontrol/policy/v2/utils.go index 370ec43eb..10641259c 100644 --- a/hscontrol/policy/v2/utils.go +++ b/hscontrol/policy/v2/utils.go @@ -31,8 +31,8 @@ var ( // // Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types. // Bracket stripping reduces both forms to bare "addr:port" or "addr/prefix:port", -// which the normal [strings.LastIndex] of ":" split handles correctly because -// port strings never contain colons. +// which the normal LastIndex(":") split handles correctly because port strings +// never contain colons. func splitDestinationAndPort(input string) (string, string, error) { // Handle RFC 3986 bracketed IPv6 (e.g. "[::1]:80" or "[fd7a::1]/128:80,443"). // Strip brackets after validation and fall through to normal parsing. @@ -82,7 +82,7 @@ func splitDestinationAndPort(input string) (string, string, error) { return destination, port, nil } -// parsePortRange parses a port definition string and returns a slice of [tailcfg.PortRange] structs. +// parsePortRange parses a port definition string and returns a slice of PortRange structs. func parsePortRange(portDef string) ([]tailcfg.PortRange, error) { if portDef == "*" { return []tailcfg.PortRange{tailcfg.PortRangeAny}, nil diff --git a/hscontrol/policy/v2/utils_test.go b/hscontrol/policy/v2/utils_test.go index 3a02bc660..393a3b1fa 100644 --- a/hscontrol/policy/v2/utils_test.go +++ b/hscontrol/policy/v2/utils_test.go @@ -8,7 +8,7 @@ import ( "tailscale.com/tailcfg" ) -// TestSplitDestinationAndPort tests the splitDestinationAndPort function using table-driven tests. +// TestParseDestinationAndPort tests the splitDestinationAndPort function using table-driven tests. func TestSplitDestinationAndPort(t *testing.T) { testCases := []struct { input string diff --git a/hscontrol/poll.go b/hscontrol/poll.go index 35657026c..bb98f6acd 100644 --- a/hscontrol/poll.go +++ b/hscontrol/poll.go @@ -115,7 +115,7 @@ func (m *mapSession) serve() { // This is the mechanism where the node gives us information about its // current configuration. // - // Process the [tailcfg.MapRequest] to update node state (endpoints, hostinfo, etc.) + // Process the MapRequest to update node state (endpoints, hostinfo, etc.) c, err := m.h.state.UpdateNodeFromMapRequest(m.node.ID, m.req) if err != nil { httpError(m.w, err) @@ -148,10 +148,10 @@ func (m *mapSession) serveLongPoll() { m.log.Trace().Caller().Msg("long poll session started") - // connectGen is set by [state.State.Connect] below and captured by the deferred cleanup closure. - // Each Connect acquires one live session in state; the cleanup must release - // it with exactly one [state.State.Disconnect] call, in every exit path, or - // the node's session count leaks and it stays online forever. + // connectGen is set by Connect() below and captured by the deferred cleanup closure. + // It allows Disconnect() to reject stale calls from old sessions — if a newer session + // has called Connect() (incrementing the generation), the old session's Disconnect() + // sees a mismatched generation and becomes a no-op. var connectGen uint64 // Clean up the session when the client disconnects @@ -160,55 +160,49 @@ func (m *mapSession) serveLongPoll() { stillConnected := m.h.mapBatcher.RemoveNode(m.node.ID, m.ch) - // This session never reached [state.State.Connect]; there is no - // session to release. - if connectGen == 0 { + // If another session already exists for this node (reconnect + // happened before this cleanup ran), skip the grace period + // entirely — the node is not actually disconnecting. + if stillConnected { return } // When a node disconnects, it might rapidly reconnect (e.g. mobile clients, network weather). // Instead of immediately marking the node as offline, we wait a few seconds to see if it reconnects. - // If it reconnects during the wait, the new session's Connect raises the - // session count, so the release below keeps the node online. + // If it does reconnect, the existing mapSession will be replaced and the node remains online. + // If it doesn't reconnect within the timeout, we mark it as offline. // // This avoids flapping nodes in the UI and unnecessary churn in the network. // This is not my favourite solution, but it kind of works in our eventually consistent world. - // - // When another session already replaced this one (stillConnected), skip - // the wait — but never the release itself. A cancelled map request whose - // handler ran late is exactly such a session: if it kept its session - // acquired on this path, the surviving session's release could never - // take the node offline (the relogin flake). - if !stillConnected { - // Wait up to 10 seconds for the node to reconnect. - // 10 seconds was arbitrary chosen as a reasonable time to reconnect. - ticker := time.NewTicker(time.Second) - defer ticker.Stop() + ticker := time.NewTicker(time.Second) + defer ticker.Stop() - for range 10 { - if m.h.mapBatcher.IsConnected(m.node.ID) { - break - } - - <-ticker.C + disconnected := true + // Wait up to 10 seconds for the node to reconnect. + // 10 seconds was arbitrary chosen as a reasonable time to reconnect. + for range 10 { + if m.h.mapBatcher.IsConnected(m.node.ID) { + disconnected = false + break } + + <-ticker.C } - // Release this session. The node goes offline exactly when the last - // live session is released, so releases from replaced or stale - // sessions are harmless regardless of the order they run in. - disconnectChanges, err := m.h.state.Disconnect(m.node.ID, connectGen) - if err != nil { - m.log.Error().Caller().Err(err).Msg("failed to disconnect node") - } + if disconnected { + // Pass the generation from our Connect() call. If a newer session has + // connected since (bumping the generation), Disconnect() will detect + // the mismatch and skip the state update, preventing the race where + // an old grace period goroutine overwrites a newer session's online status. + disconnectChanges, err := m.h.state.Disconnect(m.node.ID, connectGen) + if err != nil { + m.log.Error().Caller().Err(err).Msg("failed to disconnect node") + } - if len(disconnectChanges) == 0 { - return + m.h.Change(disconnectChanges...) + m.afterServeLongPoll() + m.log.Info().Caller().Str(zf.Chan, fmt.Sprintf("%p", m.ch)).Msg("node has disconnected") } - - m.h.Change(disconnectChanges...) - m.afterServeLongPoll() - m.log.Info().Caller().Str(zf.Chan, fmt.Sprintf("%p", m.ch)).Msg("node has disconnected") }() // Set up the client stream @@ -220,28 +214,23 @@ func (m *mapSession) serveLongPoll() { m.keepAliveTicker = time.NewTicker(m.keepAlive) - // Process the initial [tailcfg.MapRequest] to update node state (endpoints, hostinfo, etc.) - // This must be done BEFORE calling [state.State.Connect] to ensure routes are properly synchronized. - // When nodes reconnect, they send their hostinfo with announced routes in the [tailcfg.MapRequest]. - // We need this data in [state.NodeStore] before [state.State.Connect] sets up the primary routes, because - // [types.NodeView.SubnetRoutes] calculates the intersection of announced and approved routes. If we - // call [state.State.Connect] first, [types.NodeView.SubnetRoutes] returns empty (no announced routes yet), causing + // Process the initial MapRequest to update node state (endpoints, hostinfo, etc.) + // This must be done BEFORE calling Connect() to ensure routes are properly synchronized. + // When nodes reconnect, they send their hostinfo with announced routes in the MapRequest. + // We need this data in NodeStore before Connect() sets up the primary routes, because + // SubnetRoutes() calculates the intersection of announced and approved routes. If we + // call Connect() first, SubnetRoutes() returns empty (no announced routes yet), causing // the node to be incorrectly removed from AvailableRoutes. mapReqChange, err := m.h.state.UpdateNodeFromMapRequest(m.node.ID, m.req) if err != nil { m.log.Error().Caller().Err(err).Msg("failed to update node from initial MapRequest") - // Write an explicit error rather than returning silently: a bare - // return leaves net/http to send an empty 200, which the client - // reads as "unexpected EOF" and retries forever (issue #3346). - httpError(m.w, err) - return } // Connect the node after its state has been updated. // We send two separate change notifications because these are distinct operations: - // 1. [state.State.UpdateNodeFromMapRequest]: processes the client's reported state (routes, endpoints, hostinfo) - // 2. [state.State.Connect]: marks the node online and recalculates primary routes based on the updated state + // 1. UpdateNodeFromMapRequest: processes the client's reported state (routes, endpoints, hostinfo) + // 2. Connect: marks the node online and recalculates primary routes based on the updated state // While this results in two notifications, it ensures route data is synchronized before // primary route selection occurs, which is critical for proper HA subnet router failover. var connectChanges []change.Change @@ -257,11 +246,6 @@ func (m *mapSession) serveLongPoll() { // time between the node connecting and the batcher being ready. if err := m.h.mapBatcher.AddNode(m.node.ID, m.ch, m.capVer, m.stopFromBatcher); err != nil { //nolint:noinlineerr m.log.Error().Caller().Err(err).Msg("failed to add node to batcher") - // Write an explicit error rather than returning silently: a bare - // return leaves net/http to send an empty 200, which the client - // reads as "unexpected EOF" and retries forever (issue #3346). - httpError(m.w, err) - return } @@ -324,8 +308,8 @@ func (m *mapSession) serveLongPoll() { // writeMap writes the map response to the client. // It handles compression if requested and any headers that need to be set. -// It also handles flushing the response if the [http.ResponseWriter] -// implements [http.Flusher]. +// It also handles flushing the response if the ResponseWriter +// implements http.Flusher. func (m *mapSession) writeMap(msg *tailcfg.MapResponse) error { jsonBody, err := json.Marshal(msg) if err != nil { diff --git a/hscontrol/poll_test.go b/hscontrol/poll_test.go index 71ce08d9a..4fa78f475 100644 --- a/hscontrol/poll_test.go +++ b/hscontrol/poll_test.go @@ -7,10 +7,8 @@ import ( "testing" "time" - "github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/mapper" "github.com/juanfont/headscale/hscontrol/state" - "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types/change" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -91,131 +89,6 @@ func (w *delayedSuccessResponseWriter) WriteCount() int { return w.writeCount } -// recordingResponseWriter records the status code and whether anything was -// written, so a test can tell an explicit error response apart from a handler -// that returned without writing (which net/http turns into an empty 200 the -// client reads as "unexpected EOF"). -type recordingResponseWriter struct { - mu sync.Mutex - header http.Header - status int - writes int -} - -func (w *recordingResponseWriter) Header() http.Header { - if w.header == nil { - w.header = make(http.Header) - } - - return w.header -} - -func (w *recordingResponseWriter) WriteHeader(code int) { - w.mu.Lock() - defer w.mu.Unlock() - - if w.status == 0 { - w.status = code - } -} - -func (w *recordingResponseWriter) Write(data []byte) (int, error) { - w.mu.Lock() - defer w.mu.Unlock() - - if w.status == 0 { - w.status = http.StatusOK - } - - w.writes++ - - return len(data), nil -} - -func (w *recordingResponseWriter) Flush() {} - -func (w *recordingResponseWriter) statusCode() int { - w.mu.Lock() - defer w.mu.Unlock() - - return w.status -} - -// TestServeLongPollWritesErrorWhenInitialMapFails proves that when the initial -// map cannot be generated (here: the node's own GivenName is invalid, so -// WithSelfNode fails and AddNode errors), serveLongPoll writes an explicit HTTP -// error instead of returning with no body. Returning empty leaves net/http to -// send an empty 200, which the Tailscale client reports as -// "PollNetMap: ... unexpected EOF" and retries forever (issue #3346). -func TestServeLongPollWritesErrorWhenInitialMapFails(t *testing.T) { - app := createTestApp(t) - user := app.state.CreateUserForTest("self-bad-name-user") - createdNode := app.state.CreateRegisteredNodeForTest(user, "self-bad-name-node") - - // Corrupt the node's stored name to empty so GetFQDN fails for itself, - // then reload state so the bad row enters the NodeStore verbatim. - app.mapBatcher.Close() - require.NoError(t, app.state.Close()) - - database, err := db.NewHeadscaleDatabase(app.cfg) - require.NoError(t, err) - require.NoError(t, database.DB. - Model(&types.Node{}). - Where("id = ?", createdNode.ID). - Update("given_name", "").Error) - require.NoError(t, database.Close()) - - app.state, err = state.NewState(app.cfg) - require.NoError(t, err) - - app.mapBatcher = mapper.NewBatcherAndMapper(app.cfg, app.state) - app.mapBatcher.Start() - - t.Cleanup(func() { - app.mapBatcher.Close() - require.NoError(t, app.state.Close()) - }) - - nodeView, ok := app.state.GetNodeByID(createdNode.ID) - require.True(t, ok) - - node := nodeView.AsStruct() - - ctx, cancel := context.WithCancel(context.Background()) - writer := &recordingResponseWriter{} - session := app.newMapSession(ctx, tailcfg.MapRequest{ - Stream: true, - Version: tailcfg.CapabilityVersion(100), - }, writer, node) - - serveDone := make(chan struct{}) - - go func() { - session.serveLongPoll() - close(serveDone) - }() - - t.Cleanup(func() { - // Break the post-disconnect reconnect wait so the goroutine exits. - dummyCh := make(chan *tailcfg.MapResponse, 1) - _ = app.mapBatcher.AddNode(node.ID, dummyCh, tailcfg.CapabilityVersion(100), nil) - - cancel() - - select { - case <-serveDone: - case <-time.After(2 * time.Second): - } - - _ = app.mapBatcher.RemoveNode(node.ID, dummyCh) - }) - - assert.Eventually(t, func() bool { - return writer.statusCode() >= http.StatusInternalServerError - }, 2*time.Second, 10*time.Millisecond, - "serveLongPoll must write an HTTP error response when the initial map cannot be built, not an empty 200") -} - // TestGitHubIssue3129_TransientlyBlockedWriteDoesNotLeaveLiveStaleSession // tests the scenario reported in // https://github.com/juanfont/headscale/issues/3129. @@ -227,7 +100,7 @@ func TestServeLongPollWritesErrorWhenInitialMapFails(t *testing.T) { // 3. While that write is blocked, queue enough updates to fill the buffered // channel and make the next batcher send hit the stale-send timeout. // 4. That stale-send path removes the session from the batcher, so without an -// explicit teardown hook the old [mapSession.serveLongPoll] goroutine would stay alive +// explicit teardown hook the old serveLongPoll goroutine would stay alive // but stop receiving future updates. // 5. Release the blocked write and verify the batcher-side stop signal makes // that stale session exit instead of lingering as an orphaned goroutine. diff --git a/hscontrol/realip.go b/hscontrol/realip.go deleted file mode 100644 index af41aa7bd..000000000 --- a/hscontrol/realip.go +++ /dev/null @@ -1,121 +0,0 @@ -package hscontrol - -import ( - "fmt" - "net" - "net/http" - "net/netip" - "slices" - - realclientip "github.com/realclientip/realclientip-go" -) - -const ( - headerTrueClientIP = "True-Client-IP" - headerXRealIP = "X-Real-IP" - headerXForwardedFor = "X-Forwarded-For" -) - -var proxyHeaders = [...]string{headerTrueClientIP, headerXRealIP, headerXForwardedFor} - -// trustedProxyRealIP rewrites r.RemoteAddr from proxy headers when the -// peer is in trusted; for any other peer the headers are stripped so a -// downstream handler cannot read a spoofed value. X-Forwarded-For uses -// [realclientip.RightmostTrustedRangeStrategy] so prepending a value cannot win in a -// proxy chain. -func trustedProxyRealIP(trusted []netip.Prefix) (func(http.Handler) http.Handler, error) { - ranges := make([]net.IPNet, 0, len(trusted)) - for _, p := range trusted { - ranges = append(ranges, prefixToIPNet(p)) - } - - trueClientIP, err := realclientip.NewSingleIPHeaderStrategy(headerTrueClientIP) - if err != nil { - return nil, fmt.Errorf("%s strategy: %w", headerTrueClientIP, err) - } - - xRealIP, err := realclientip.NewSingleIPHeaderStrategy(headerXRealIP) - if err != nil { - return nil, fmt.Errorf("%s strategy: %w", headerXRealIP, err) - } - - xForwardedFor, err := realclientip.NewRightmostTrustedRangeStrategy(headerXForwardedFor, ranges) - if err != nil { - return nil, fmt.Errorf("%s strategy: %w", headerXForwardedFor, err) - } - - strategy := realclientip.NewChainStrategy(trueClientIP, xRealIP, xForwardedFor) - - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !peerTrusted(r.RemoteAddr, trusted) { - for _, h := range proxyHeaders { - r.Header.Del(h) - } - - next.ServeHTTP(w, r) - - return - } - - // Proxy headers carry no port; write the IP alone so - // `remote=` logs the resolved client, not the proxy's - // ephemeral TCP port. - if ip := strategy.ClientIP(r.Header, r.RemoteAddr); ip != "" { - r.RemoteAddr = ip - } - - next.ServeHTTP(w, r) - }) - }, nil -} - -// peerTrusted returns false on unparseable input so callers fall -// through to the header-stripping path. -func peerTrusted(remoteAddr string, trusted []netip.Prefix) bool { - addr, ok := parsePeerAddr(remoteAddr) - if !ok { - return false - } - - return slices.ContainsFunc(trusted, func(p netip.Prefix) bool { - return p.Contains(addr) - }) -} - -func parsePeerAddr(remoteAddr string) (netip.Addr, bool) { - if remoteAddr == "" { - return netip.Addr{}, false - } - - ap, err := netip.ParseAddrPort(remoteAddr) - if err == nil { - return ap.Addr(), true - } - - host, _, splitErr := net.SplitHostPort(remoteAddr) - if splitErr != nil { - host = remoteAddr - } - - addr, err := netip.ParseAddr(host) - if err != nil { - return netip.Addr{}, false - } - - return addr, true -} - -// prefixToIPNet bridges to realclientip-go, which predates net/netip. -func prefixToIPNet(p netip.Prefix) net.IPNet { - addr := p.Addr() - if addr.Is4() { - b := addr.As4() - - return net.IPNet{IP: b[:], Mask: net.CIDRMask(p.Bits(), 32)} - } - - b := addr.As16() - - return net.IPNet{IP: b[:], Mask: net.CIDRMask(p.Bits(), 128)} -} diff --git a/hscontrol/realip_test.go b/hscontrol/realip_test.go deleted file mode 100644 index 2d3b3371e..000000000 --- a/hscontrol/realip_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package hscontrol - -import ( - "net/http" - "net/http/httptest" - "net/netip" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -//nolint:goconst // repeated test fixtures (addresses, headers), not refactor candidates -func TestPeerTrusted(t *testing.T) { - trusted := []netip.Prefix{ - netip.MustParsePrefix("10.0.0.0/16"), - netip.MustParsePrefix("127.0.0.1/32"), - netip.MustParsePrefix("fd00::/8"), - } - - tests := []struct { - name string - remoteAddr string - want bool - }{ - {name: "v4-in-range", remoteAddr: "10.0.0.5:1234", want: true}, - {name: "v4-edge", remoteAddr: "10.0.255.255:1", want: true}, - {name: "v4-out-of-range", remoteAddr: "10.1.0.0:1234", want: false}, - {name: "v4-loopback", remoteAddr: "127.0.0.1:443", want: true}, - {name: "v6-in-range", remoteAddr: "[fd00::1]:443", want: true}, - {name: "v6-out-of-range", remoteAddr: "[2001:db8::1]:443", want: false}, - {name: "no-port", remoteAddr: "10.0.0.5", want: true}, - {name: "empty", remoteAddr: "", want: false}, - {name: "non-ip-host", remoteAddr: "localhost:8080", want: false}, - {name: "garbage", remoteAddr: "not-a-thing", want: false}, - {name: "unix-socket", remoteAddr: "@", want: false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := peerTrusted(tt.remoteAddr, trusted) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestPrefixToIPNet(t *testing.T) { - tests := []struct { - name string - in netip.Prefix - want string - }{ - {name: "v4", in: netip.MustParsePrefix("10.0.0.0/16"), want: "10.0.0.0/16"}, - {name: "v4-host", in: netip.MustParsePrefix("127.0.0.1/32"), want: "127.0.0.1/32"}, - {name: "v6", in: netip.MustParsePrefix("fd00::/8"), want: "fd00::/8"}, - {name: "v6-host", in: netip.MustParsePrefix("::1/128"), want: "::1/128"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := prefixToIPNet(tt.in) - assert.Equal(t, tt.want, got.String()) - }) - } -} - -//nolint:goconst // repeated test fixtures (addresses, headers), not refactor candidates -func TestTrustedProxyRealIP(t *testing.T) { - trusted := []netip.Prefix{ - netip.MustParsePrefix("10.0.0.0/16"), - netip.MustParsePrefix("fd00::/8"), - } - - mw, err := trustedProxyRealIP(trusted) - require.NoError(t, err) - - tests := []struct { - name string - remoteAddr string - headers map[string]string - wantRemote string - wantStripped []string - wantKept map[string]string - }{ - { - name: "untrusted/no-headers", - remoteAddr: "203.0.113.1:1234", - wantRemote: "203.0.113.1:1234", - }, - { - name: "untrusted/strips-x-real-ip", - remoteAddr: "203.0.113.1:1234", - headers: map[string]string{"X-Real-IP": "1.2.3.4"}, - wantRemote: "203.0.113.1:1234", - wantStripped: []string{"X-Real-IP"}, - }, - { - name: "untrusted/strips-x-forwarded-for", - remoteAddr: "203.0.113.1:1234", - headers: map[string]string{"X-Forwarded-For": "1.2.3.4"}, - wantRemote: "203.0.113.1:1234", - wantStripped: []string{"X-Forwarded-For"}, - }, - { - name: "untrusted/strips-true-client-ip", - remoteAddr: "203.0.113.1:1234", - headers: map[string]string{"True-Client-IP": "1.2.3.4"}, - wantRemote: "203.0.113.1:1234", - wantStripped: []string{"True-Client-IP"}, - }, - { - name: "untrusted/strips-all-three", - remoteAddr: "203.0.113.1:1234", - headers: map[string]string{ - "True-Client-IP": "1.2.3.4", - "X-Real-IP": "5.6.7.8", - "X-Forwarded-For": "9.10.11.12", - }, - wantRemote: "203.0.113.1:1234", - wantStripped: []string{"True-Client-IP", "X-Real-IP", "X-Forwarded-For"}, - }, - { - name: "untrusted/keeps-unrelated-header", - remoteAddr: "203.0.113.1:1234", - headers: map[string]string{"User-Agent": "curl/8", "X-Real-IP": "1.2.3.4"}, - wantRemote: "203.0.113.1:1234", - wantStripped: []string{"X-Real-IP"}, - wantKept: map[string]string{"User-Agent": "curl/8"}, - }, - { - name: "trusted/no-headers", - remoteAddr: "10.0.0.5:1234", - wantRemote: "10.0.0.5:1234", - }, - { - name: "trusted/x-real-ip", - remoteAddr: "10.0.0.5:1234", - headers: map[string]string{"X-Real-IP": "1.2.3.4"}, - wantRemote: "1.2.3.4", - }, - { - name: "trusted/true-client-ip-wins-over-others", - remoteAddr: "10.0.0.5:1234", - headers: map[string]string{ - "True-Client-IP": "1.2.3.4", - "X-Real-IP": "5.6.7.8", - "X-Forwarded-For": "9.10.11.12", - }, - wantRemote: "1.2.3.4", - }, - { - name: "trusted/x-real-ip-wins-over-xff", - remoteAddr: "10.0.0.5:1234", - headers: map[string]string{ - "X-Real-IP": "1.2.3.4", - "X-Forwarded-For": "9.10.11.12", - }, - wantRemote: "1.2.3.4", - }, - { - name: "trusted/xff-rightmost-walk-discards-trusted-hop", - remoteAddr: "10.0.0.5:1234", - headers: map[string]string{"X-Forwarded-For": "203.0.113.99, 10.0.0.5"}, - wantRemote: "203.0.113.99", - }, - { - name: "trusted/xff-all-trusted-leaves-remote-alone", - remoteAddr: "10.0.0.5:1234", - headers: map[string]string{"X-Forwarded-For": "10.0.0.99, 10.0.0.5"}, - wantRemote: "10.0.0.5:1234", - }, - { - name: "trusted/ipv6-peer-v6-real-ip", - remoteAddr: "[fd00::1]:1234", - headers: map[string]string{"X-Real-IP": "2001:db8::1"}, - wantRemote: "2001:db8::1", - }, - { - name: "ipv6-untrusted-strips-header", - remoteAddr: "[2001:db8::1]:1234", - headers: map[string]string{"X-Real-IP": "1.2.3.4"}, - wantRemote: "[2001:db8::1]:1234", - wantStripped: []string{"X-Real-IP"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var observed *http.Request - - handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - observed = r - })) - - req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil) - req.RemoteAddr = tt.remoteAddr - - for k, v := range tt.headers { - req.Header.Set(k, v) - } - - handler.ServeHTTP(httptest.NewRecorder(), req) - - require.NotNil(t, observed, "handler must be invoked") - assert.Equal(t, tt.wantRemote, observed.RemoteAddr) - - for _, h := range tt.wantStripped { - assert.Empty(t, observed.Header.Get(h), "header %s should be stripped", h) - } - - for k, v := range tt.wantKept { - assert.Equal(t, v, observed.Header.Get(k), "header %s should be preserved", k) - } - }) - } -} - -func TestTrustedProxyRealIPEmptyTrusted(t *testing.T) { - // Sanity: factory accepts an empty slice without error. Wiring code is - // responsible for skipping the mount entirely, but the factory itself - // must remain safe for tests that compose it manually. - mw, err := trustedProxyRealIP(nil) - require.NoError(t, err) - require.NotNil(t, mw) - - var observed *http.Request - - handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - observed = r - })) - - req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil) - req.RemoteAddr = "10.0.0.5:1234" - req.Header.Set("X-Real-IP", "1.2.3.4") - handler.ServeHTTP(httptest.NewRecorder(), req) - - require.NotNil(t, observed) - // No prefix is trusted, so even an LAN-looking peer is not trusted; the - // spoofed header must be stripped and RemoteAddr left alone. - assert.Equal(t, "10.0.0.5:1234", observed.RemoteAddr) - assert.Empty(t, observed.Header.Get("X-Real-IP")) -} diff --git a/hscontrol/scope/scope.go b/hscontrol/scope/scope.go deleted file mode 100644 index 1a1a0a6d0..000000000 --- a/hscontrol/scope/scope.go +++ /dev/null @@ -1,123 +0,0 @@ -// Package scope models the OAuth capability scopes the Headscale v2 API enforces -// and the rule for whether a granted set of scopes satisfies a required one. -// -// The vocabulary is taken from Tailscale's OpenAPI spec (the same scope names -// the Terraform provider and Kubernetes operator request), so a client written -// against Tailscale's scopes works unchanged against Headscale. The grant -// predicate is kept here, separate from the HTTP/huma layer in -// hscontrol/api/v2, so it can be tested exhaustively on its own. -package scope - -import "strings" - -// Scope is an OAuth capability an operation requires and a token grants. The names -// mirror Tailscale's API scopes; a "...:read" scope is the read-only subset of its -// write scope. -type Scope string - -const ( - // All and AllRead are Tailscale's forward-compatible super-scopes: "all" - // grants every other scope, "all:read" grants every :read subset. - All Scope = "all" - AllRead Scope = "all:read" - - AuthKeys Scope = "auth_keys" - AuthKeysRead Scope = "auth_keys:read" - - // OAuthKeys gates managing OAuth clients (keyType:"client" on the keys - // resource). - OAuthKeys Scope = "oauth_keys" - OAuthKeysRead Scope = "oauth_keys:read" - - DevicesCore Scope = "devices:core" - DevicesCoreRead Scope = "devices:core:read" - - DevicesRoutes Scope = "devices:routes" - DevicesRoutesRead Scope = "devices:routes:read" - - PolicyFile Scope = "policy_file" - PolicyFileRead Scope = "policy_file:read" - - FeatureSettings Scope = "feature_settings" - FeatureSettingsRead Scope = "feature_settings:read" - - Users Scope = "users" - UsersRead Scope = "users:read" -) - -const readSuffix = ":read" - -// Known returns every scope in the vocabulary, in a stable order. Useful for -// exhaustive iteration in tests and documentation. -func Known() []Scope { - return []Scope{ - All, AllRead, - AuthKeys, AuthKeysRead, - OAuthKeys, OAuthKeysRead, - DevicesCore, DevicesCoreRead, - DevicesRoutes, DevicesRoutesRead, - PolicyFile, PolicyFileRead, - FeatureSettings, FeatureSettingsRead, - Users, UsersRead, - } -} - -// IsRead reports whether s is a read-only scope (its name ends with ":read"). -func (s Scope) IsRead() bool { - return strings.HasSuffix(string(s), readSuffix) -} - -// IsWrite reports whether s is a non-empty write scope. -func (s Scope) IsWrite() bool { - return s != "" && !s.IsRead() -} - -// Parse converts scope strings (as stored on a token or client) into Scope values. -// Unknown strings are kept as-is; they simply never satisfy any required scope. -func Parse(ss []string) []Scope { - out := make([]Scope, len(ss)) - for i, s := range ss { - out[i] = Scope(s) - } - - return out -} - -// Grants reports whether the granted scopes satisfy the required want scope. -func Grants(granted []Scope, want Scope) bool { - for _, g := range granted { - if satisfies(g, want) { - return true - } - } - - return false -} - -// satisfies reports whether a single held scope satisfies want: exact match; a -// write scope grants its own :read subset; "all" grants everything; "all:read" -// grants any :read scope. -func satisfies(have, want Scope) bool { - if have == want || have == All { - return true - } - - if have == AllRead { - return want.IsRead() - } - - // A write scope grants its own read subset, e.g. auth_keys ⊇ auth_keys:read. - return string(want) == string(have)+readSuffix -} - -// RequiresTags reports whether any scope obliges a credential to carry tags: -// devices:core and auth_keys mint tagged, tailnet-owned credentials. -func RequiresTags(scopes []Scope) bool { - for _, s := range scopes { - if s == DevicesCore || s == AuthKeys { - return true - } - } - - return false -} diff --git a/hscontrol/scope/scope_property_test.go b/hscontrol/scope/scope_property_test.go deleted file mode 100644 index 1132ba513..000000000 --- a/hscontrol/scope/scope_property_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package scope - -import ( - "slices" - "testing" - - "pgregory.net/rapid" -) - -// scopeGen draws a scope: mostly from the real vocabulary, sometimes adversarial -// junk (including read-like junk such as "foo:read") so the rules are exercised -// against unknown input too. -func scopeGen() *rapid.Generator[Scope] { - known := Known() - - return rapid.Custom(func(t *rapid.T) Scope { - if rapid.Float64().Draw(t, "junkP") < 0.2 { - return Scope(rapid.StringMatching(`[a-z_]{1,12}(:read)?`).Draw(t, "junk")) - } - - return rapid.SampledFrom(known).Draw(t, "vocab") - }) -} - -// TestGrantsMatchesOracle fuzzes Grants against the independent oracle over random -// granted-sets and want-scopes (vocabulary + junk). -func TestGrantsMatchesOracle(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - granted := rapid.SliceOfN(scopeGen(), 0, 6).Draw(rt, "granted") - want := scopeGen().Draw(rt, "want") - - if got, exp := Grants(granted, want), oracleGrants(granted, want); got != exp { - rt.Fatalf("Grants(%v, %q) = %v, oracle = %v", granted, want, got, exp) - } - }) -} - -// TestGrantsInvariants asserts the algebraic properties of the grant relation hold -// for arbitrary inputs. -func TestGrantsInvariants(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - granted := rapid.SliceOfN(scopeGen(), 0, 6).Draw(rt, "granted") - want := scopeGen().Draw(rt, "want") - - // Reflexivity: a scope always grants itself. - if !Grants([]Scope{want}, want) { - rt.Fatalf("reflexivity: %q does not grant itself", want) - } - - // The empty set grants nothing. - if Grants(nil, want) { - rt.Fatalf("empty grant satisfied %q", want) - } - - // OR-semantics: a set grants iff some member does. - anyMember := slices.ContainsFunc(granted, func(g Scope) bool { - return Grants([]Scope{g}, want) - }) - if Grants(granted, want) != anyMember { - rt.Fatalf("OR-semantics broken for %v / %q", granted, want) - } - - // Monotonicity: adding a scope never withdraws a grant. - before := Grants(granted, want) - extra := scopeGen().Draw(rt, "extra") - after := Grants(append(slices.Clone(granted), extra), want) - - if before && !after { - rt.Fatalf("monotonicity broken: adding %q withdrew the grant of %q", extra, want) - } - }) -} - -// TestSuperScopeProperties fuzzes the super-scope rules. -func TestSuperScopeProperties(t *testing.T) { - rapid.Check(t, func(rt *rapid.T) { - want := scopeGen().Draw(rt, "want") - - // all grants everything. - if !Grants([]Scope{All}, want) { - rt.Fatalf("all did not grant %q", want) - } - - // all:read grants exactly the read scopes. - if Grants([]Scope{AllRead}, want) != want.IsRead() { - rt.Fatalf("all:read grant of %q = %v, want IsRead = %v", - want, Grants([]Scope{AllRead}, want), want.IsRead()) - } - }) -} diff --git a/hscontrol/scope/scope_test.go b/hscontrol/scope/scope_test.go deleted file mode 100644 index 0b59aa42a..000000000 --- a/hscontrol/scope/scope_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package scope - -import ( - "fmt" - "strings" - "testing" -) - -// classify decomposes a scope into (resource, super, read) WITHOUT reusing any of -// the production logic, so the oracle below is an independent second -// implementation of the grant rule: a divergence between it and Grants is a real -// bug in one of them, not a tautology. -type classified struct { - resource string // "" for super-scopes; otherwise the write-scope base (e.g. "auth_keys") - super bool // all / all:read - read bool // the :read variant -} - -func classify(s Scope) classified { - str := string(s) - read := strings.HasSuffix(str, ":read") - base := strings.TrimSuffix(str, ":read") - - if base == "all" { - return classified{super: true, read: read} - } - - return classified{resource: base, read: read} -} - -// oracle re-derives "does have satisfy want" from the classification, independent -// of satisfies/Grants. -func oracle(have, want Scope) bool { - h, w := classify(have), classify(want) - - if h.super { - // "all" grants everything; "all:read" grants only reads. - return !h.read || w.read - } - - if h.resource != w.resource { - return false - } - - // Same resource: a write scope grants both read and write; a read scope grants - // only read. - return !h.read || w.read -} - -func oracleGrants(granted []Scope, want Scope) bool { - for _, g := range granted { - if oracle(g, want) { - return true - } - } - - return false -} - -// TestGrantsHandPicked pins specific (granted, want) outcomes with literal -// expected values, independent of any oracle: the anchor for the rules. -func TestGrantsHandPicked(t *testing.T) { - tests := []struct { - granted []Scope - want Scope - ok bool - }{ - {granted: []Scope{AuthKeys}, want: AuthKeys, ok: true}, - {granted: []Scope{AuthKeys}, want: AuthKeysRead, ok: true}, - {granted: []Scope{AuthKeysRead}, want: AuthKeys, ok: false}, - {granted: []Scope{AuthKeysRead}, want: AuthKeysRead, ok: true}, - {granted: []Scope{DevicesCore}, want: AuthKeys, ok: false}, - {granted: []Scope{DevicesCoreRead}, want: AuthKeysRead, ok: false}, - {granted: []Scope{All}, want: AuthKeys, ok: true}, - {granted: []Scope{All}, want: FeatureSettingsRead, ok: true}, - {granted: []Scope{AllRead}, want: PolicyFileRead, ok: true}, - {granted: []Scope{AllRead}, want: PolicyFile, ok: false}, - {granted: []Scope{AllRead}, want: All, ok: false}, - {granted: []Scope{DevicesCore, OAuthKeys}, want: OAuthKeys, ok: true}, - {granted: nil, want: AuthKeysRead, ok: false}, - {granted: []Scope{"garbage"}, want: AuthKeys, ok: false}, - {granted: []Scope{"garbage"}, want: "garbage", ok: true}, - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%v_%s", tt.granted, tt.want), func(t *testing.T) { - if got := Grants(tt.granted, tt.want); got != tt.ok { - t.Errorf("Grants(%v, %q) = %v, want %v", tt.granted, tt.want, got, tt.ok) - } - }) - } -} - -// TestGrantsExhaustive checks every single-grant pair in the vocabulary against -// the independent oracle, plus representative multi-grant cases. -func TestGrantsExhaustive(t *testing.T) { - known := Known() - - for _, g := range known { - for _, w := range known { - got := Grants([]Scope{g}, w) - exp := oracle(g, w) - - if got != exp { - t.Errorf("Grants([%q], %q) = %v, oracle = %v", g, w, got, exp) - } - } - } - - multi := [][]Scope{ - {All, AuthKeysRead}, - {AllRead, AuthKeys}, - {AuthKeys, OAuthKeysRead}, - {DevicesCore, DevicesRoutes, PolicyFile}, - {AuthKeys, AuthKeys}, // duplicates - } - - for _, granted := range multi { - for _, w := range known { - got := Grants(granted, w) - exp := oracleGrants(granted, w) - - if got != exp { - t.Errorf("Grants(%v, %q) = %v, oracle = %v", granted, w, got, exp) - } - } - } -} - -// TestWriteGrantsItsRead and friends assert the structural rules over the whole -// vocabulary, deterministically. -func TestWriteGrantsItsRead(t *testing.T) { - for _, s := range Known() { - if !s.IsWrite() { - continue - } - - read := Scope(string(s) + ":read") - if !Grants([]Scope{s}, read) { - t.Errorf("write scope %q does not grant its read subset %q", s, read) - } - } -} - -func TestReadNeverGrantsWrite(t *testing.T) { - for _, s := range Known() { - if !s.IsRead() { - continue - } - - write := Scope(strings.TrimSuffix(string(s), ":read")) - if Grants([]Scope{s}, write) { - t.Errorf("read scope %q must not grant write scope %q", s, write) - } - } -} - -func TestAllGrantsEverything(t *testing.T) { - for _, w := range Known() { - if !Grants([]Scope{All}, w) { - t.Errorf("all should grant %q", w) - } - } -} - -func TestAllReadGrantsReadsOnly(t *testing.T) { - for _, w := range Known() { - got := Grants([]Scope{AllRead}, w) - if got != w.IsRead() { - t.Errorf("all:read grants %q = %v, want %v (IsRead)", w, got, w.IsRead()) - } - } -} - -// TestResourceIsolation: a non-super scope never grants a scope of a different -// resource. -func TestResourceIsolation(t *testing.T) { - for _, a := range Known() { - if a == All || a == AllRead { - continue - } - - for _, b := range Known() { - if classify(a).resource == classify(b).resource { - continue - } - - if Grants([]Scope{a}, b) { - t.Errorf("scope %q (resource %q) must not grant %q (resource %q)", - a, classify(a).resource, b, classify(b).resource) - } - } - } -} - -func TestRequiresTags(t *testing.T) { - tests := []struct { - scopes []Scope - requires bool - }{ - {scopes: []Scope{DevicesCore}, requires: true}, - {scopes: []Scope{AuthKeys}, requires: true}, - {scopes: []Scope{OAuthKeys}, requires: false}, - {scopes: []Scope{PolicyFile, AuthKeys}, requires: true}, - {scopes: []Scope{DevicesCoreRead}, requires: false}, - {scopes: nil, requires: false}, - } - - for _, tt := range tests { - t.Run(fmt.Sprintf("%v", tt.scopes), func(t *testing.T) { - if got := RequiresTags(tt.scopes); got != tt.requires { - t.Errorf("RequiresTags(%v) = %v, want %v", tt.scopes, got, tt.requires) - } - }) - } -} - -func TestKnownIsComplete(t *testing.T) { - known := Known() - - seen := make(map[Scope]bool, len(known)) - for _, s := range known { - if seen[s] { - t.Errorf("Known() contains duplicate %q", s) - } - - seen[s] = true - } - - // 7 resources × 2 (write+read) + 2 super-scopes = 16. - if len(known) != 16 { - t.Errorf("Known() has %d scopes, want 16", len(known)) - } -} diff --git a/hscontrol/servertest/apiv2_devices_test.go b/hscontrol/servertest/apiv2_devices_test.go deleted file mode 100644 index 5d85a46f9..000000000 --- a/hscontrol/servertest/apiv2_devices_test.go +++ /dev/null @@ -1,457 +0,0 @@ -package servertest_test - -import ( - "fmt" - "net/url" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - tsclient "tailscale.com/client/tailscale/v2" -) - -// baselinePolicy declares tag:ci so device SetTags is permitted. Every policy -// the device/acl tests write keeps tag:ci, so subtest order does not matter. -const baselinePolicy = `{"tagOwners":{"tag:ci":["apiv2@"]},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - -// setBaselinePolicy installs baselinePolicy into both the policy manager and the -// database so device tagging and the ACL reads work. -func setBaselinePolicy(t *testing.T, srv *servertest.TestServer) { - t.Helper() - - st := srv.State() - - _, err := st.SetPolicy([]byte(baselinePolicy)) - require.NoError(t, err) - - _, err = st.SetPolicyInDB(baselinePolicy) - require.NoError(t, err) - - _, err = st.ReloadPolicy() - require.NoError(t, err) -} - -func goClient(t *testing.T, baseURL, apiKey string) *tsclient.Client { - t.Helper() - - base, err := url.Parse(baseURL) - require.NoError(t, err) - - return &tsclient.Client{BaseURL: base, APIKey: apiKey, Tailnet: "-"} -} - -// srvNodeView reads the node straight from the server's NodeStore — the -// authoritative state between client steps. Handlers mutate the NodeStore -// synchronously before responding, so a read right after a 2xx is consistent. -func srvNodeView(t *testing.T, srv *servertest.TestServer, id types.NodeID) types.NodeView { - t.Helper() - - v, ok := srv.State().GetNodeByID(id) - require.Truef(t, ok, "node %d must exist server-side", id) - require.True(t, v.Valid()) - - return v -} - -func approvedRoutesOf(nv types.NodeView) []string { - return util.PrefixesToString(nv.ApprovedRoutes().AsSlice()) -} - -func nodeListed(srv *servertest.TestServer, id types.NodeID) bool { - for _, n := range srv.State().ListNodes().All() { - if n.ID() == id { - return true - } - } - - return false -} - -// apiv2DevicesGoClient drives the device lifecycle through the official SDK, -// validating each mutation three ways: the tool's own get-after-set, the -// server-side NodeStore, and (where relevant) permutations. Tagging is done late -// because it clears user ownership; delete is last. -func apiv2DevicesGoClient(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, id types.NodeID) { - t.Helper() - - ctx := t.Context() - dr := goClient(t, baseURL, apiKey).Devices() - deviceID := strconv.FormatUint(uint64(id), 10) - - // Get — tool and server agree on identity and addresses. - dev, err := dr.Get(ctx, deviceID) - require.NoError(t, err) - assert.Equal(t, deviceID, dev.NodeID) - assert.NotEmpty(t, dev.Addresses) - assert.True(t, dev.Authorized) - assert.Equal(t, srvNodeView(t, srv, id).IPsAsString(), dev.Addresses) - assert.Equal(t, srvNodeView(t, srv, id).GivenName(), dev.Name) - - // List — present in both the tool list and the server's node list. - devs, err := dr.List(ctx) - require.NoError(t, err) - assert.True(t, containsDevice(devs, deviceID), "created device present in list") - assert.True(t, nodeListed(srv, id)) - - // SetName — get-after-set + server-side, then a second rename. - require.NoError(t, dr.SetName(ctx, deviceID, "renamed-go")) - dev, err = dr.Get(ctx, deviceID) - require.NoError(t, err) - assert.Equal(t, "renamed-go", dev.Name) - assert.Equal(t, "renamed-go", srvNodeView(t, srv, id).GivenName()) - - require.NoError(t, dr.SetName(ctx, deviceID, "renamed-go-2")) - assert.Equal(t, "renamed-go-2", srvNodeView(t, srv, id).GivenName()) - - // SetSubnetRoutes — one, two, then exit (expands to both families). - require.NoError(t, dr.SetSubnetRoutes(ctx, deviceID, []string{"10.0.0.0/24"})) - routes, err := dr.SubnetRoutes(ctx, deviceID) - require.NoError(t, err) - assert.Contains(t, routes.Enabled, "10.0.0.0/24") - assert.Contains(t, approvedRoutesOf(srvNodeView(t, srv, id)), "10.0.0.0/24") - assert.Empty(t, srvNodeView(t, srv, id).AnnouncedRoutes(), "route enabled without being announced") - - require.NoError(t, dr.SetSubnetRoutes(ctx, deviceID, []string{"10.0.0.0/24", "192.168.0.0/24"})) - approved := approvedRoutesOf(srvNodeView(t, srv, id)) - assert.Contains(t, approved, "10.0.0.0/24") - assert.Contains(t, approved, "192.168.0.0/24") - - // A single exit prefix expands to both families on the server. - require.NoError(t, dr.SetSubnetRoutes(ctx, deviceID, []string{"0.0.0.0/0"})) - approved = approvedRoutesOf(srvNodeView(t, srv, id)) - assert.Contains(t, approved, "0.0.0.0/0") - assert.Contains(t, approved, "::/0") - - // SetKey — seed a real expiry first so disabling it is a state transition. - future := time.Now().Add(24 * time.Hour) - _, _, err = srv.State().SetNodeExpiry(id, &future) - require.NoError(t, err) - require.True(t, srvNodeView(t, srv, id).Expiry().Valid()) - - require.NoError(t, dr.SetKey(ctx, deviceID, tsclient.DeviceKey{KeyExpiryDisabled: true})) - dev, err = dr.Get(ctx, deviceID) - require.NoError(t, err) - assert.True(t, dev.KeyExpiryDisabled) - assert.False(t, srvNodeView(t, srv, id).Expiry().Valid()) - - // Re-enable is a no-op; expiry stays cleared. - require.NoError(t, dr.SetKey(ctx, deviceID, tsclient.DeviceKey{KeyExpiryDisabled: false})) - assert.False(t, srvNodeView(t, srv, id).Expiry().Valid()) - - // SetTags — flips ownership to the tags; the user is dropped. - require.NoError(t, dr.SetTags(ctx, deviceID, []string{"tag:ci"})) - dev, err = dr.Get(ctx, deviceID) - require.NoError(t, err) - assert.Equal(t, []string{"tag:ci"}, dev.Tags) - assert.Equal(t, types.TaggedDevices.Username(), dev.User) - n := srvNodeView(t, srv, id) - assert.True(t, n.IsTagged()) - assert.Equal(t, []string{"tag:ci"}, n.Tags().AsSlice()) - assert.False(t, n.User().Valid()) - - // Re-tagging with the same tag is idempotent. - require.NoError(t, dr.SetTags(ctx, deviceID, []string{"tag:ci"})) - assert.Equal(t, []string{"tag:ci"}, srvNodeView(t, srv, id).Tags().AsSlice()) - - // SetAuthorized(true) is a no-op success; de-auth is rejected and inert. - require.NoError(t, dr.SetAuthorized(ctx, deviceID, true)) - dev, err = dr.Get(ctx, deviceID) - require.NoError(t, err) - assert.True(t, dev.Authorized) - - require.Error(t, dr.SetAuthorized(ctx, deviceID, false), "de-authorization is unsupported") - assert.True(t, srvNodeView(t, srv, id).Valid(), "rejected de-auth left the node present") - - // Delete — gone from the tool and the server. - require.NoError(t, dr.Delete(ctx, deviceID)) - _, err = dr.Get(ctx, deviceID) - assert.Truef(t, tsclient.IsNotFound(err), "get after delete should be 404, got %v", err) - - _, ok := srv.State().GetNodeByID(id) - assert.False(t, ok, "deleted node is gone server-side") -} - -func containsDevice(devs []tsclient.Device, id string) bool { - for _, d := range devs { - if d.NodeID == id || d.ID == id { - return true - } - } - - return false -} - -// apiv2ACLGoClient round-trips the policy file: read, raw-read, conditional set. -func apiv2ACLGoClient(t *testing.T, baseURL, apiKey string) { - t.Helper() - - ctx := t.Context() - pf := goClient(t, baseURL, apiKey).PolicyFile() - - acl, err := pf.Get(ctx) - require.NoError(t, err) - assert.NotEmpty(t, acl.ETag, "GET /acl carries an ETag") - - raw, err := pf.Raw(ctx) - require.NoError(t, err) - assert.NotEmpty(t, raw.HuJSON) - - // Set with the current etag as If-Match; keep tag:ci. - require.NoError(t, pf.Set(ctx, baselinePolicy, raw.ETag)) - - updated, err := pf.Raw(ctx) - require.NoError(t, err) - assert.Contains(t, updated.HuJSON, "tag:ci") -} - -// apiv2SettingsGoClient reads the tailnet settings (write is unsupported). -func apiv2SettingsGoClient(t *testing.T, baseURL, apiKey string) { - t.Helper() - - settings, err := goClient(t, baseURL, apiKey).TailnetSettings().Get(t.Context()) - require.NoError(t, err) - assert.Equal(t, "none", string(settings.UsersRoleAllowedToJoinExternalTailnets)) -} - -// tscliRun runs tscli and fails the test on a non-zero exit. -type tscliRun func(args ...string) string - -// tscliRunner returns runners for tscli against the local server: one that -// requires success, and one that tolerates a non-zero exit (for the expected -// 404 after a delete). -func tscliRunner(t *testing.T, baseURL, apiKey string) (tscliRun, func(args ...string) error) { - t.Helper() - - bin, err := exec.LookPath("tscli") - require.NoErrorf(t, err, "tscli is required for TestAPIv2 (provided by the nix dev shell)") - - env := append( - os.Environ(), - "TSCLI_BASE_URL="+baseURL, - "TAILSCALE_API_KEY="+apiKey, - "TAILSCALE_TAILNET=-", - ) - - cmd := func(args ...string) *exec.Cmd { - c := exec.CommandContext(t.Context(), bin, args...) - c.Env = env - - return c - } - - run := func(args ...string) string { - t.Helper() - - out, err := cmd(args...).CombinedOutput() - require.NoErrorf(t, err, "tscli %s\n%s", strings.Join(args, " "), out) - - return string(out) - } - - runAllowErr := func(args ...string) error { - t.Helper() - - return cmd(args...).Run() - } - - return run, runAllowErr -} - -// apiv2DevicesTSCLI drives the device verbs through tscli, asserting each -// mutation via get-after-set (tscli's own json output) and the server NodeStore. -func apiv2DevicesTSCLI(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, id types.NodeID) { - t.Helper() - - run, runAllowErr := tscliRunner(t, baseURL, apiKey) - deviceID := strconv.FormatUint(uint64(id), 10) - - assert.Contains(t, run("get", "device", "--device", deviceID, "-o", "json"), deviceID) - assert.True(t, nodeListed(srv, id)) - - // Name. - run("set", "device", "name", "--device", deviceID, "--name", "renamed-tscli") - assert.Contains(t, run("get", "device", "--device", deviceID, "-o", "json"), "renamed-tscli") - assert.Equal(t, "renamed-tscli", srvNodeView(t, srv, id).GivenName()) - - // Routes — one, two, then exit (both families). - run("set", "device", "routes", "--device", deviceID, "--route", "10.0.0.0/24") - assert.Contains(t, run("list", "routes", "--device", deviceID, "-o", "json"), "10.0.0.0/24") - assert.Contains(t, approvedRoutesOf(srvNodeView(t, srv, id)), "10.0.0.0/24") - - run("set", "device", "routes", "--device", deviceID, "--route", "10.0.0.0/24", "--route", "192.168.0.0/24") - - approved := approvedRoutesOf(srvNodeView(t, srv, id)) - assert.Contains(t, approved, "10.0.0.0/24") - assert.Contains(t, approved, "192.168.0.0/24") - - run("set", "device", "routes", "--device", deviceID, "--route", "0.0.0.0/0") - - exitApproved := approvedRoutesOf(srvNodeView(t, srv, id)) - assert.Contains(t, exitApproved, "0.0.0.0/0") - assert.Contains(t, exitApproved, "::/0") - - // Key — seed a real expiry first so disabling it is a transition. - future := time.Now().Add(24 * time.Hour) - _, _, err := srv.State().SetNodeExpiry(id, &future) - require.NoError(t, err) - - run("set", "device", "key", "--device", deviceID, "--disable-expiry") - assert.Contains(t, run("get", "device", "--device", deviceID, "-o", "json"), `"keyExpiryDisabled": true`) - assert.False(t, srvNodeView(t, srv, id).Expiry().Valid()) - - // Tags — flips to tag ownership. - run("set", "device", "tags", "--device", deviceID, "--tag", "tag:ci") - assert.Contains(t, run("get", "device", "--device", deviceID, "-o", "json"), "tag:ci") - n := srvNodeView(t, srv, id) - assert.True(t, n.IsTagged()) - assert.Equal(t, []string{"tag:ci"}, n.Tags().AsSlice()) - assert.False(t, n.User().Valid()) - - // Authorization — approve is a no-op success. - run("set", "device", "authorization", "--device", deviceID, "--approve") - assert.Contains(t, run("get", "device", "--device", deviceID, "-o", "json"), `"authorized": true`) - - // Delete — gone from tscli and the server. - run("delete", "device", "--device", deviceID) - require.Error(t, runAllowErr("get", "device", "--device", deviceID, "-o", "json"), "get after delete should fail") - - _, ok := srv.State().GetNodeByID(id) - assert.False(t, ok) -} - -// apiv2ACLTSCLI reads and writes the policy file through tscli. -func apiv2ACLTSCLI(t *testing.T, baseURL, apiKey string) { - t.Helper() - - run, _ := tscliRunner(t, baseURL, apiKey) - - assert.Contains(t, run("get", "policy", "--json"), "acls") - - dir := t.TempDir() - polFile := filepath.Join(dir, "policy.hujson") - require.NoError(t, os.WriteFile(polFile, []byte(baselinePolicy), 0o600)) - run("set", "policy", "--file", polFile) -} - -// apiv2SettingsTSCLI reads the tailnet settings through tscli. -func apiv2SettingsTSCLI(t *testing.T, baseURL, apiKey string) { - t.Helper() - - run, _ := tscliRunner(t, baseURL, apiKey) - assert.Contains(t, run("get", "settings", "-o", "json"), "devicesKeyDurationDays") -} - -// devicesACLTFConfig exercises Terraform device + ACL data sources AND resources. -// %s is the test node's hostname (the tailscale_device data source key). -const devicesACLTFConfig = ` -terraform { - required_providers { - tailscale = { - source = "tailscale/tailscale" - version = "~> 0.21" - } - } -} - -provider "tailscale" {} - -resource "tailscale_acl" "policy" { - acl = jsonencode({ - tagOwners = { "tag:ci" = ["apiv2@"] } - acls = [{ action = "accept", src = ["*"], dst = ["*:*"] }] - }) - overwrite_existing_content = true -} - -data "tailscale_device" "dut" { - hostname = "%s" - wait_for = "30s" -} - -data "tailscale_devices" "all" {} - -data "tailscale_acl" "current" { - depends_on = [tailscale_acl.policy] -} - -resource "tailscale_device_authorization" "dut" { - device_id = data.tailscale_device.dut.node_id - authorized = true -} - -resource "tailscale_device_tags" "dut" { - device_id = data.tailscale_device.dut.node_id - tags = ["tag:ci"] - depends_on = [tailscale_acl.policy] -} - -resource "tailscale_device_key" "dut" { - device_id = data.tailscale_device.dut.node_id - key_expiry_disabled = true -} - -resource "tailscale_device_subnet_routes" "dut" { - device_id = data.tailscale_device.dut.node_id - routes = ["10.0.0.0/24"] -} - -output "dut_node_id" { value = data.tailscale_device.dut.node_id } -output "dut_addresses" { value = data.tailscale_device.dut.addresses } -output "dut_authorized" { value = data.tailscale_device.dut.authorized } -output "device_count" { value = length(data.tailscale_devices.all.devices) } -output "acl_hujson" { value = data.tailscale_acl.current.hujson } -output "enabled_routes" { value = tailscale_device_subnet_routes.dut.routes } -` - -// apiv2DevicesACLTerraform runs a tofu init/apply/destroy over the device and -// ACL data sources and resources, asserting no post-apply drift, the data-source -// outputs (read path) against the server truth, and the resulting server state -// (write path). parallelism=1 avoids racing concurrent mutations on the one -// shared node. -func apiv2DevicesACLTerraform(t *testing.T, srv *servertest.TestServer, baseURL, apiKey, hostname string, id types.NodeID) { - t.Helper() - - tf := newTofu(t, baseURL, apiKey, fmt.Sprintf(devicesACLTFConfig, hostname)) - - tf.run("init", "-no-color", "-input=false") - tf.run("apply", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - - // Data sources resolved to real values that match the server. - outputs := tf.outputs() - assert.Equal(t, strconv.FormatUint(uint64(id), 10), outputs.str(t, "dut_node_id")) - assert.ElementsMatch(t, srvNodeView(t, srv, id).IPsAsString(), outputs.strSlice(t, "dut_addresses")) - outputs.jsonEq(t, "dut_authorized", true) - assert.Equal(t, srv.State().ListNodes().Len(), int(outputs.num(t, "device_count"))) - assert.Contains(t, outputs.str(t, "acl_hujson"), "tag:ci") - assert.Contains(t, outputs.strSlice(t, "enabled_routes"), "10.0.0.0/24") - - // Server-side: the resources actually applied (write path). - n := srvNodeView(t, srv, id) - assert.True(t, n.IsTagged()) - assert.Equal(t, []string{"tag:ci"}, n.Tags().AsSlice()) - assert.Contains(t, approvedRoutesOf(n), "10.0.0.0/24") - assert.False(t, n.Expiry().Valid()) - - pol, err := srv.State().GetPolicy() - require.NoError(t, err) - assert.Contains(t, pol.Data, "tag:ci", "tailscale_acl wrote the policy") - - // A converged config must produce an empty plan — drift is a read/write bug. - tf.assertNoDrift() - - // destroy resets the policy; the node is a data source, so it persists. - // Tags/expiry teardown are no-ops on Headscale, so they are not reverted. - tf.run("destroy", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - - _, ok := srv.State().GetNodeByID(id) - assert.True(t, ok, "data-source node persists across destroy") -} diff --git a/hscontrol/servertest/apiv2_oauth_scopes_test.go b/hscontrol/servertest/apiv2_oauth_scopes_test.go deleted file mode 100644 index 5d3cff67c..000000000 --- a/hscontrol/servertest/apiv2_oauth_scopes_test.go +++ /dev/null @@ -1,391 +0,0 @@ -package servertest_test - -import ( - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/stretchr/testify/require" -) - -// TestAPIv2OAuthScopes proves the v2 OAuth scope and tag enforcement through the -// REAL Tailscale Terraform provider's client-credentials flow: the provider mints -// a scoped access token at /api/v2/oauth/token, then runs one operation whose -// allow/deny outcome must match the token's scope and tag grant. -// -// Each client is created with exactly the scopes/tags under test, and the -// provider requests no scope narrowing, so the minted token carries the client's -// full grant. The matrix covers right-scope-allowed, wrong-scope-denied, read vs -// write, the "all" super-scope, and policy tag ownership (owned-by delegation). -// -// The oauth_keys rows drive the real provider's tailscale_oauth_client resource, -// which omits the "capabilities" body field, so the request schema must make it -// optional (it is). Drift is not asserted on the auth-key rows: the provider -// defaults preauthorized=true on create but the server reads tagged keys back as -// false, so a converged plan is never empty. That is orthogonal to scope -// enforcement, so a clean apply is the allow proof. -// -// tofu is required, not optional: it ships in the nix dev shell, so a missing -// binary means a broken environment and the test fails rather than skipping. -func TestAPIv2OAuthScopes(t *testing.T) { - srv := servertest.NewServer(t, servertest.WithRealListener()) - owner := srv.CreateUser(t, "apiv2-oauth") - creator := owner.ID - - // scopeMatrixPolicy declares every tag the matrix touches: tag:ci for the - // auth-key rows, and the tag:k8s/tag:k8s-operator delegation for the - // owned-by rows. - setScopeMatrixPolicy(t, srv) - - // A registered node is the target for the device-scope rows. Its decimal id - // is the device id the v2 API and the provider's device resources address. - // A devices:core write token also grants devices:core:read, so whatever - // get/post sequence the provider runs within the core family is satisfied; - // the read-scope row then fails on the write, proving read vs write end to - // end. devices:routes and feature_settings are proven exhaustively by the Go - // matrix (apiv2_oauth_matrix_test.go), not here: the provider's routes - // resource issues a cross-family device read a routes-only token would lack, - // and the server's settings endpoint is read-only. - deviceID := srv.CreateRegisteredNode(t, owner).StringID() - deviceAuthorizeConfig := ` -resource "tailscale_device_authorization" "d" { - device_id = "` + deviceID + `" - authorized = true -} -` - - // The configs each exercise exactly one operation so a row's allow/deny - // outcome is unambiguous. - const ( - authKeyConfig = ` -resource "tailscale_tailnet_key" "k" { - reusable = true - ephemeral = false - expiry = 3600 - description = "oauth-scope-matrix" - tags = ["tag:ci"] -} -` - aclConfig = ` -resource "tailscale_acl" "policy" { - acl = jsonencode({ - tagOwners = { - "tag:ci" = ["apiv2-oauth@"] - "tag:k8s-operator" = [] - "tag:k8s" = ["tag:k8s-operator"] - "tag:other" = [] - } - acls = [{ action = "accept", src = ["*"], dst = ["*:*"] }] - }) - overwrite_existing_content = true -} -` - devicesReadConfig = ` -data "tailscale_devices" "all" {} -output "device_count" { value = length(data.tailscale_devices.all.devices) } -` - // oauthClientConfig creates an OAuth client whose scope (oauth_keys:read) - // is within an oauth_keys grant, so the only variable under test is whether - // the caller may manage clients at all. - oauthClientConfig = ` -resource "tailscale_oauth_client" "c" { - description = "oauth-scope-matrix-client" - scopes = ["oauth_keys:read"] -} -` - // oauthClientEscalateConfig requests a scope (devices:core:read) that an - // oauth_keys-only caller does not itself hold, so the escalation guard must - // reject minting a broader client. - oauthClientEscalateConfig = ` -resource "tailscale_oauth_client" "c" { - description = "oauth-scope-matrix-escalate" - scopes = ["devices:core:read"] -} -` - ) - - // k8sAuthKeyConfig templates the auth-key tag for the owned-by rows. - k8sAuthKeyConfig := func(tag string) string { - return ` -resource "tailscale_tailnet_key" "k" { - reusable = true - ephemeral = false - expiry = 3600 - description = "oauth-scope-matrix" - tags = ["` + tag + `"] -} -` - } - - tests := []struct { - name string - // scopes/tags the OAuth client (and thus its minted token) holds. - scopes []string - tags []string - // config is the single-operation HCL applied through the OAuth provider. - config string - // deny asserts apply fails; denyContains lists substrings, any of which - // the failure output must contain. allow asserts a clean apply. - deny bool - denyContains []string - }{ - // auth_keys: write scope mints an auth key; read/wrong/all-read do not. - { - name: "auth_keys allows tailnet_key", - scopes: []string{"auth_keys"}, - tags: []string{"tag:ci"}, - config: authKeyConfig, - }, - { - name: "auth_keys:read denies tailnet_key", - scopes: []string{"auth_keys:read"}, - tags: []string{"tag:ci"}, - config: authKeyConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - { - name: "devices:core denies tailnet_key", - scopes: []string{"devices:core"}, - tags: []string{"tag:ci"}, - config: authKeyConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - { - name: "all allows tailnet_key", - scopes: []string{"all"}, - tags: []string{"tag:ci"}, - config: authKeyConfig, - }, - { - name: "all:read denies tailnet_key", - scopes: []string{"all:read"}, - tags: []string{"tag:ci"}, - config: authKeyConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - - // oauth_keys: managing OAuth clients. Read and wrong scopes are denied, and - // a caller cannot mint a client carrying authority it lacks (escalation). - { - name: "oauth_keys allows oauth_client", - scopes: []string{"oauth_keys"}, - config: oauthClientConfig, - }, - { - name: "oauth_keys:read denies oauth_client", - scopes: []string{"oauth_keys:read"}, - config: oauthClientConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - { - name: "auth_keys denies oauth_client", - scopes: []string{"auth_keys"}, - tags: []string{"tag:ci"}, - config: oauthClientConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - { - name: "oauth_keys cannot escalate client scope", - scopes: []string{"oauth_keys"}, - config: oauthClientEscalateConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "beyond the creating token"}, - }, - - // policy_file: write scope sets the ACL; read does not. - { - name: "policy_file allows acl", - scopes: []string{"policy_file"}, - config: aclConfig, - }, - { - name: "policy_file:read denies acl", - scopes: []string{"policy_file:read"}, - config: aclConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - - // devices:core:read reads the devices data source. - { - name: "devices:core:read allows devices read", - scopes: []string{"devices:core:read"}, - config: devicesReadConfig, - }, - { - name: "policy_file:read denies devices read", - scopes: []string{"policy_file:read"}, - config: devicesReadConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - - // devices:core: a write-scoped token authorizes a device; the read scope - // and a wrong-family scope are denied (read vs write through the real - // provider's tailscale_device_authorization resource). - { - name: "devices:core allows device authorize", - scopes: []string{"devices:core"}, - tags: []string{"tag:ci"}, - config: deviceAuthorizeConfig, - }, - { - name: "devices:core:read denies device authorize", - scopes: []string{"devices:core:read"}, - config: deviceAuthorizeConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - { - name: "policy_file denies device authorize", - scopes: []string{"policy_file"}, - config: deviceAuthorizeConfig, - deny: true, - denyContains: []string{"403", "Forbidden", "missing the required scope"}, - }, - - // Tag owned-by: a tag:k8s-operator client may use its own tag and the - // tag:k8s it owns, but not an unowned tag. - { - name: "owned-by exact tag allowed", - scopes: []string{"auth_keys"}, - tags: []string{"tag:k8s-operator"}, - config: k8sAuthKeyConfig("tag:k8s-operator"), - }, - { - name: "owned-by delegated tag allowed", - scopes: []string{"auth_keys"}, - tags: []string{"tag:k8s-operator"}, - config: k8sAuthKeyConfig("tag:k8s"), - }, - { - name: "owned-by unowned tag denied", - scopes: []string{"auth_keys"}, - tags: []string{"tag:k8s-operator"}, - config: k8sAuthKeyConfig("tag:other"), - deny: true, - denyContains: []string{"403", "Forbidden", "is not owned"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - secret, client, err := srv.State().CreateOAuthClient(tt.scopes, tt.tags, "scope-matrix", &creator) - require.NoError(t, err) - - tf := newTofuOAuth(t, srv.URL, client.ClientID, secret, oauthHCL(tt.config)) - tf.run("init", "-no-color", "-input=false") - - if tt.deny { - tf.runExpectError(t, tt.denyContains...) - return - } - - tf.run("apply", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - }) - } -} - -// setScopeMatrixPolicy installs the policy the scope matrix relies on: tag:ci for -// the auth-key rows, and the tag:k8s-operator → tag:k8s delegation for the -// owned-by rows. -func setScopeMatrixPolicy(t *testing.T, srv *servertest.TestServer) { - t.Helper() - - // tag:other exists but is owned by no one, so the owned-by denial row tests a - // grant denial (403) rather than a tag-not-in-policy rejection (400). - const policy = `{"tagOwners":{"tag:ci":["apiv2-oauth@"],"tag:k8s-operator":[],"tag:k8s":["tag:k8s-operator"],"tag:other":[]},"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` - - st := srv.State() - - _, err := st.SetPolicy([]byte(policy)) - require.NoError(t, err) - - _, err = st.SetPolicyInDB(policy) - require.NoError(t, err) - - _, err = st.ReloadPolicy() - require.NoError(t, err) -} - -// oauthHCL wraps a single-operation body with the provider block. The provider -// authenticates via the OAuth env vars newTofuOAuth sets, so the block is empty. -func oauthHCL(body string) string { - return ` -terraform { - required_providers { - tailscale = { - source = "tailscale/tailscale" - version = "~> 0.21" - } - } -} - -provider "tailscale" {} -` + body -} - -// newTofuOAuth is a newTofu variant whose provider authenticates with OAuth -// client credentials instead of an API key: it sets TAILSCALE_OAUTH_CLIENT_ID / -// TAILSCALE_OAUTH_CLIENT_SECRET (the env vars the tailscale/tailscale provider -// honors), so the real provider runs the client-credentials grant against -// baseURL/api/v2/oauth/token. The client id is embedded in the secret, so the -// secret embeds the client id (the provider sends both; the server derives the -// client from the secret alone). -func newTofuOAuth(t *testing.T, baseURL, clientID, clientSecret, config string) *tofu { - t.Helper() - - bin, err := exec.LookPath("tofu") - require.NoErrorf(t, err, "tofu is required for TestAPIv2OAuthScopes (provided by the nix dev shell)") - - dir := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(dir, "main.tf"), []byte(config), 0o600)) - require.NoError(t, os.MkdirAll(filepath.Join(dir, "plugin-cache"), 0o755)) - - env := append( - os.Environ(), - "TAILSCALE_BASE_URL="+baseURL, - "TAILSCALE_OAUTH_CLIENT_ID="+clientID, - "TAILSCALE_OAUTH_CLIENT_SECRET="+clientSecret, - "TAILSCALE_TAILNET=-", - // Keep provider plugins inside the temp dir so the run is self-contained. - "TF_PLUGIN_CACHE_DIR="+filepath.Join(dir, "plugin-cache"), - ) - - cmd := func(args ...string) *exec.Cmd { - c := exec.CommandContext(t.Context(), bin, args...) - c.Dir = dir - c.Env = env - - return c - } - - return &tofu{t: t, cmd: cmd} -} - -// runExpectError runs apply expecting FAILURE, asserting the combined output -// contains at least one of mustContain. This is the deny half of every matrix -// row: a scoped token attempting an operation it lacks the scope (or tag) for. -func (tf *tofu) runExpectError(t *testing.T, mustContain ...string) { - t.Helper() - - out, err := tf.cmd("apply", "-auto-approve", "-no-color", "-input=false", "-parallelism=1").CombinedOutput() - require.Errorf(t, err, "expected apply to fail, but it succeeded:\n%s", out) - - combined := string(out) - for _, want := range mustContain { - if strings.Contains(combined, want) { - return - } - } - - t.Fatalf("apply failed but output contained none of %v:\n%s", mustContain, combined) -} diff --git a/hscontrol/servertest/apiv2_test.go b/hscontrol/servertest/apiv2_test.go deleted file mode 100644 index 4fb98d10c..000000000 --- a/hscontrol/servertest/apiv2_test.go +++ /dev/null @@ -1,578 +0,0 @@ -package servertest_test - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - - "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - tsclient "tailscale.com/client/tailscale/v2" -) - -// TestAPIv2 proves the v2 API's Tailscale-compatible (ported) endpoints against the three real -// clients it exists to support: the official Go SDK, tscli, and the Tailscale -// Terraform provider (via OpenTofu). All three run against one Headscale bound -// to a real loopback port, authenticating with a user-owned API key. -// -// Every mutation is validated three ways — the tool's own get-after-set, the -// server-side NodeStore/state, and (for Terraform) a no-change plan proving no -// drift — so a server/provider read-write mismatch fails loudly. -// -// tscli and tofu are required, not optional: they ship in the nix dev shell, so -// a missing binary means a broken environment and the test fails rather than -// silently skipping. -func TestAPIv2(t *testing.T) { - srv := servertest.NewServer(t, servertest.WithRealListener()) - owner := srv.CreateUser(t, "apiv2") - apiKey := srv.CreateAPIKey(t, owner) - - // tag:ci must exist in policy for device SetTags; every policy the tests - // write keeps it, so subtest order is irrelevant. Terraform runs last so its - // ACL teardown does not strand the others. - setBaselinePolicy(t, srv) - - t.Run("GoClient", func(t *testing.T) { - apiv2GoClient(t, srv, srv.URL, apiKey, owner) - apiv2UsersGoClient(t, srv, srv.URL, apiKey, owner) - - node := srv.CreateRegisteredNode(t, owner, "dut-go") - apiv2DevicesGoClient(t, srv, srv.URL, apiKey, node.ID()) - apiv2ACLGoClient(t, srv.URL, apiKey) - apiv2SettingsGoClient(t, srv.URL, apiKey) - }) - - t.Run("TSCLI", func(t *testing.T) { - apiv2TSCLI(t, srv, srv.URL, apiKey) - apiv2UsersTSCLI(t, srv.URL, apiKey, owner) - - node := srv.CreateRegisteredNode(t, owner, "dut-tscli") - apiv2DevicesTSCLI(t, srv, srv.URL, apiKey, node.ID()) - apiv2ACLTSCLI(t, srv.URL, apiKey) - apiv2SettingsTSCLI(t, srv.URL, apiKey) - }) - - t.Run("Terraform", func(t *testing.T) { - apiv2Terraform(t, srv, srv.URL, apiKey, owner) - apiv2UsersTerraform(t, srv, srv.URL, apiKey, owner) - - node := srv.CreateRegisteredNode(t, owner, "dut-tf") - apiv2DevicesACLTerraform(t, srv, srv.URL, apiKey, node.Hostname(), node.ID()) - }) -} - -// apiv2GoClient exercises the official SDK with untagged (user-owned) keys — the -// default Terraform/tscli path — validating each operation against the server's -// stored PreAuthKey, plus ephemeral and default-expiry permutations. -func apiv2GoClient(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, owner *types.User) { - t.Helper() - - ctx := t.Context() - keys := goClient(t, baseURL, apiKey).Keys() - wantOwner := strconv.FormatUint(uint64(owner.ID), 10) - - var req tsclient.CreateKeyRequest - - req.Description = "go-client" - req.ExpirySeconds = 3600 - req.Capabilities.Devices.Create.Reusable = true - - created, err := keys.CreateAuthKey(ctx, req) - require.NoError(t, err) - assert.NotEmpty(t, created.ID) - assert.NotEmpty(t, created.Key, "secret returned on create") - assert.Equal(t, "go-client", created.Description) - assert.Equal(t, wantOwner, created.UserID, "user-owned key reports its owner") - - // Server-side: the stored key matches the request and is owned by the user. - pak := srvPreAuthKey(t, srv, created.ID) - assert.True(t, pak.Reusable) - assert.False(t, pak.Ephemeral) - assert.Empty(t, pak.Tags, "no tags -> user-owned") - require.NotNil(t, pak.User) - assert.Equal(t, owner.ID, pak.User.ID) - assert.Equal(t, "go-client", pak.Description) - require.NotNil(t, pak.CreatedAt) - require.NotNil(t, pak.Expiration) - assert.InDelta(t, 3600, pak.Expiration.Sub(*pak.CreatedAt).Seconds(), 5) - - got, err := keys.Get(ctx, created.ID) - require.NoError(t, err) - assert.Equal(t, created.ID, got.ID) - assert.Empty(t, got.Key, "secret omitted on get") - assert.Equal(t, "go-client", got.Description) - assert.False(t, got.Invalid) - // The SDK decodes our integer expirySeconds into a Duration of nanoseconds, - // so never assert it numerically; the lifetime rides on Expires-Created. - assert.InDelta(t, 3600, got.Expires.Sub(got.Created).Seconds(), 5) - - list, err := keys.List(ctx, true) - require.NoError(t, err) - assert.True(t, containsKeyID(list, created.ID), "created key present in list") - - // DELETE soft-revokes (Tailscale-faithful): the key stays retrievable, now - // invalid, until the collector reaps it. - require.NoError(t, keys.Delete(ctx, created.ID)) - revoked, err := keys.Get(ctx, created.ID) - require.NoError(t, err, "revoked key stays retrievable") - assert.True(t, revoked.Invalid, "revoked key reports invalid") - require.NotNil(t, srvPreAuthKey(t, srv, created.ID).Revoked, "key soft-revoked server-side") - - // Permutation — ephemeral key. - var ephReq tsclient.CreateKeyRequest - - ephReq.Capabilities.Devices.Create.Ephemeral = true - eph, err := keys.CreateAuthKey(ctx, ephReq) - require.NoError(t, err) - assert.True(t, srvPreAuthKey(t, srv, eph.ID).Ephemeral) - require.NoError(t, keys.Delete(ctx, eph.ID)) - - // Permutation — default expiry (omit ExpirySeconds -> 90 days). - def, err := keys.CreateAuthKey(ctx, tsclient.CreateKeyRequest{}) - require.NoError(t, err) - defKey := srvPreAuthKey(t, srv, def.ID) - require.NotNil(t, defKey.CreatedAt) - require.NotNil(t, defKey.Expiration) - assert.InDelta(t, 7776000, defKey.Expiration.Sub(*defKey.CreatedAt).Seconds(), 5) - require.NoError(t, keys.Delete(ctx, def.ID)) -} - -func containsKeyID(keys []tsclient.Key, id string) bool { - for _, k := range keys { - if k.ID == id { - return true - } - } - - return false -} - -func containsUserID(users []tsclient.User, id string) bool { - for _, u := range users { - if u.ID == id { - return true - } - } - - return false -} - -// srvUserCount is the server-side ground truth for the number of users. -func srvUserCount(t *testing.T, srv *servertest.TestServer) int { - t.Helper() - - users, err := srv.State().ListAllUsers() - require.NoError(t, err) - - return len(users) -} - -// apiv2UsersGoClient exercises the Users data sources through the official SDK: -// get-by-id, list, the type/role filters (member matches all, anything else -// matches nothing), and a typed 404 — each cross-checked against server truth. -func apiv2UsersGoClient(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, owner *types.User) { - t.Helper() - - ctx := t.Context() - ur := goClient(t, baseURL, apiKey).Users() - ownerID := strconv.FormatUint(uint64(owner.ID), 10) - - got, err := ur.Get(ctx, ownerID) - require.NoError(t, err) - assert.Equal(t, ownerID, got.ID) - assert.Equal(t, owner.Username(), got.LoginName) - assert.Equal(t, tsclient.UserTypeMember, got.Type) - assert.Equal(t, tsclient.UserStatusActive, got.Status) - assert.Equal(t, srv.State().ListNodesByUser(types.UserID(owner.ID)).Len(), got.DeviceCount, - "deviceCount matches the server's node count for the user") - - all, err := ur.List(ctx, nil, nil) - require.NoError(t, err) - assert.True(t, containsUserID(all, ownerID), "owner present in user list") - assert.Len(t, all, srvUserCount(t, srv)) - - // member matches every Headscale user; shared/admin match nothing. - members, err := ur.List(ctx, new(tsclient.UserTypeMember), nil) - require.NoError(t, err) - assert.Len(t, members, len(all)) - - shared, err := ur.List(ctx, new(tsclient.UserTypeShared), nil) - require.NoError(t, err) - assert.Empty(t, shared, "Headscale has no shared users") - - admins, err := ur.List(ctx, nil, new(tsclient.UserRoleAdmin)) - require.NoError(t, err) - assert.Empty(t, admins, "Headscale has no admin-role users") - - _, err = ur.Get(ctx, "999999") - require.Error(t, err) - assert.True(t, tsclient.IsNotFound(err), "unknown user id is a typed 404") -} - -// apiv2UsersTSCLI exercises the user verbs through tscli, asserting the owner is -// present in the list and retrievable by id. -func apiv2UsersTSCLI(t *testing.T, baseURL, apiKey string, owner *types.User) { - t.Helper() - - run, _ := tscliRunner(t, baseURL, apiKey) - ownerID := strconv.FormatUint(uint64(owner.ID), 10) - - listOut := run("list", "users", "-o", "json") - assert.Contains(t, listOut, ownerID) - assert.Contains(t, listOut, `"member"`) - - getOut := run("get", "user", "--user", ownerID, "-o", "json") - assert.Contains(t, getOut, ownerID) - assert.Contains(t, getOut, owner.Username()) -} - -// srvPreAuthKey is the server-side ground truth for a key id; it fails the test -// if the key is absent. -func srvPreAuthKey(t *testing.T, srv *servertest.TestServer, id string) types.PreAuthKey { - t.Helper() - - pak := findPAKByID(t, srv, id) - require.NotNilf(t, pak, "pre-auth key %s not found server-side", id) - - return *pak -} - -// findPAKByID returns the stored key with the given stringified id, or nil. -func findPAKByID(t *testing.T, srv *servertest.TestServer, id string) *types.PreAuthKey { - t.Helper() - - want, err := strconv.ParseUint(id, 10, 64) - require.NoError(t, err) - - keys, err := srv.State().ListPreAuthKeys() - require.NoError(t, err) - - for i := range keys { - if keys[i].ID == want { - return &keys[i] - } - } - - return nil -} - -// apiv2TSCLI exercises tscli with a tagged key, validating server-side that the -// stored key carries the requested tags and metadata. -func apiv2TSCLI(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string) { - t.Helper() - - run, _ := tscliRunner(t, baseURL, apiKey) - - out := run( - "create", "key", - "--type", "authkey", - "--description", "tscli", - "--expiry", "1h", - "--reusable", - "--tags", "tag:ci", - "-o", "json", - ) - - var created struct { - ID string `json:"id"` - Key string `json:"key"` - } - require.NoErrorf(t, json.Unmarshal([]byte(out), &created), "tscli create output: %s", out) - assert.NotEmpty(t, created.ID) - assert.NotEmpty(t, created.Key, "secret returned on create") - - // Server-side: tagged, reusable, described, no owner. - pak := srvPreAuthKey(t, srv, created.ID) - assert.Equal(t, []string{"tag:ci"}, pak.Tags) - assert.True(t, pak.Reusable) - assert.Equal(t, "tscli", pak.Description) - assert.Nil(t, pak.User, "tagged key has no owning user") - - getOut := run("get", "key", "--key", created.ID, "-o", "json") - - var got struct { - Key string `json:"key"` - } - require.NoError(t, json.Unmarshal([]byte(getOut), &got)) - assert.Empty(t, got.Key, "secret omitted on get") - - assert.Contains(t, run("list", "keys", "--all", "-o", "json"), created.ID) - - // DELETE soft-revokes: the key stays retrievable (invalid) server-side until - // the collector reaps it. - run("delete", "key", "--key", created.ID) - assert.Contains(t, run("get", "key", "--key", created.ID, "-o", "json"), `"invalid": true`) - require.NotNil(t, srvPreAuthKey(t, srv, created.ID).Revoked, "key soft-revoked server-side") -} - -// terraformConfig drives the tailscale_tailnet_key resource against the local -// server. No tags, so the key is owned by the API key's user — the default -// Terraform path. Outputs expose the provider's read-back for value + drift -// checks. -const terraformConfig = ` -terraform { - required_providers { - tailscale = { - source = "tailscale/tailscale" - version = "~> 0.21" - } - } -} - -provider "tailscale" {} - -resource "tailscale_tailnet_key" "test" { - reusable = true - ephemeral = false - preauthorized = true - expiry = 3600 - description = "tofu-roundtrip" -} - -output "key_id" { value = tailscale_tailnet_key.test.id } -output "key_reusable" { value = tailscale_tailnet_key.test.reusable } -output "key_ephemeral" { value = tailscale_tailnet_key.test.ephemeral } -output "key_description" { value = tailscale_tailnet_key.test.description } -` - -// apiv2Terraform runs a tofu init→apply→(no-drift)→destroy roundtrip on a -// tailnet key, cross-checking the provider outputs and the server's stored key. -func apiv2Terraform(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, owner *types.User) { - t.Helper() - - tf := newTofu(t, baseURL, apiKey, terraformConfig) - - tf.run("init", "-no-color", "-input=false") - tf.run("apply", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - - outputs := tf.outputs() - keyID := outputs.str(t, "key_id") - require.NotEmpty(t, keyID) - outputs.jsonEq(t, "key_reusable", true) - outputs.jsonEq(t, "key_ephemeral", false) - outputs.jsonEq(t, "key_description", "tofu-roundtrip") - - // Server-side: the key exists, user-owned, with the requested attributes. - pak := srvPreAuthKey(t, srv, keyID) - assert.Equal(t, "tofu-roundtrip", pak.Description) - assert.True(t, pak.Reusable) - assert.False(t, pak.Ephemeral) - assert.Empty(t, pak.Tags, "no tags -> user-owned key") - require.NotNil(t, pak.User) - assert.Equal(t, owner.ID, pak.User.ID) - require.NotNil(t, pak.CreatedAt) - require.NotNil(t, pak.Expiration) - assert.InDelta(t, 3600, pak.Expiration.Sub(*pak.CreatedAt).Seconds(), 5) - - // A converged config must produce an empty plan — drift is a read/write bug. - tf.assertNoDrift() - - // destroy DELETEs the key, which soft-revokes it: the row is kept (revoked) - // until the collector reaps it. - tf.run("destroy", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - require.NotNil(t, srvPreAuthKey(t, srv, keyID).Revoked, "key revoked after destroy") -} - -// usersTFConfig drives the tailscale_user (by login name) and tailscale_users -// data sources, plus tailscale_4via6 (provider-local compute, no server call) to -// prove that data source resolves against Headscale unchanged. %s is the owner's -// login name. Data sources create nothing, so the assertions are value -// correctness plus no drift on re-read. -const usersTFConfig = ` -terraform { - required_providers { - tailscale = { - source = "tailscale/tailscale" - version = "~> 0.21" - } - } -} - -provider "tailscale" {} - -data "tailscale_user" "owner" { - login_name = "%s" -} - -data "tailscale_users" "all" {} - -data "tailscale_4via6" "site" { - site = 7 - cidr = "10.1.1.0/24" -} - -output "user_id" { value = data.tailscale_user.owner.id } -output "user_login_name" { value = data.tailscale_user.owner.login_name } -output "user_type" { value = data.tailscale_user.owner.type } -output "user_device_count" { value = data.tailscale_user.owner.device_count } -output "users_count" { value = length(data.tailscale_users.all.users) } -output "via6" { value = data.tailscale_4via6.site.ipv6 } -` - -// apiv2UsersTerraform runs a tofu init/apply/(no-drift)/destroy over the -// tailscale_user + tailscale_users data sources (and the provider-local -// tailscale_4via6), cross-checking the data-source outputs against the server's -// stored users. -func apiv2UsersTerraform(t *testing.T, srv *servertest.TestServer, baseURL, apiKey string, owner *types.User) { - t.Helper() - - tf := newTofu(t, baseURL, apiKey, fmt.Sprintf(usersTFConfig, owner.Username())) - - tf.run("init", "-no-color", "-input=false") - tf.run("apply", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - - outputs := tf.outputs() - assert.Equal(t, strconv.FormatUint(uint64(owner.ID), 10), outputs.str(t, "user_id")) - assert.Equal(t, owner.Username(), outputs.str(t, "user_login_name")) - assert.Equal(t, "member", outputs.str(t, "user_type")) - assert.Equal(t, srv.State().ListNodesByUser(types.UserID(owner.ID)).Len(), - int(outputs.num(t, "user_device_count"))) - assert.Equal(t, srvUserCount(t, srv), int(outputs.num(t, "users_count"))) - - // tailscale_4via6 is computed by the provider with no server call; assert it - // resolved to a Tailscale 4via6 address. - assert.Contains(t, outputs.str(t, "via6"), "fd7a:115c:a1e0", "4via6 mapped address") - - // A converged data-source read must produce an empty plan — drift is a read bug. - tf.assertNoDrift() - - // destroy removes only TF state; the users persist (they are data sources). - tf.run("destroy", "-auto-approve", "-no-color", "-input=false", "-parallelism=1") - assert.GreaterOrEqual(t, srvUserCount(t, srv), 1, "users persist across data-source destroy") -} - -// tofu binds a tofu binary, working dir, and env for a single workspace. cmd is -// a closure capturing the looked-up binary so subprocess construction stays in -// one place. -type tofu struct { - t *testing.T - cmd func(args ...string) *exec.Cmd -} - -func newTofu(t *testing.T, baseURL, apiKey, config string) *tofu { - t.Helper() - - bin, err := exec.LookPath("tofu") - require.NoErrorf(t, err, "tofu is required for TestAPIv2 (provided by the nix dev shell)") - - dir := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(dir, "main.tf"), []byte(config), 0o600)) - require.NoError(t, os.MkdirAll(filepath.Join(dir, "plugin-cache"), 0o755)) - - env := append( - os.Environ(), - "TAILSCALE_BASE_URL="+baseURL, - "TAILSCALE_API_KEY="+apiKey, - "TAILSCALE_TAILNET=-", - // Keep provider plugins inside the temp dir so the run is self-contained. - "TF_PLUGIN_CACHE_DIR="+filepath.Join(dir, "plugin-cache"), - ) - - cmd := func(args ...string) *exec.Cmd { - c := exec.CommandContext(t.Context(), bin, args...) - c.Dir = dir - c.Env = env - - return c - } - - return &tofu{t: t, cmd: cmd} -} - -func (tf *tofu) run(args ...string) string { - tf.t.Helper() - - out, err := tf.cmd(args...).CombinedOutput() - require.NoErrorf(tf.t, err, "tofu %s\n%s", strings.Join(args, " "), out) - - return string(out) -} - -// assertNoDrift fails if a no-change plan reports changes. plan -// -detailed-exitcode returns 0 = no changes, 1 = error, 2 = drift. -func (tf *tofu) assertNoDrift() { - tf.t.Helper() - - out, err := tf.cmd("plan", "-detailed-exitcode", "-no-color", "-input=false", "-parallelism=1").CombinedOutput() - if err == nil { - return - } - - var exit *exec.ExitError - require.ErrorAsf(tf.t, err, &exit, "tofu plan\n%s", out) - require.Equalf(tf.t, 0, exit.ExitCode(), - "no-change plan after apply must be empty; drift means a provider read disagrees with desired state:\n%s", out) -} - -func (tf *tofu) outputs() tofuOutputs { - tf.t.Helper() - - out := tf.run("output", "-json", "-no-color") - - var raw map[string]struct { - Value json.RawMessage `json:"value"` - } - require.NoError(tf.t, json.Unmarshal([]byte(out), &raw)) - - o := make(tofuOutputs, len(raw)) - for k, v := range raw { - o[k] = v.Value - } - - return o -} - -// tofuOutputs is the decoded `tofu output -json`, keyed by output name. -type tofuOutputs map[string]json.RawMessage - -func (o tofuOutputs) raw(t *testing.T, key string) json.RawMessage { - t.Helper() - - v, ok := o[key] - require.Truef(t, ok, "output %q missing", key) - - return v -} - -func (o tofuOutputs) str(t *testing.T, key string) string { - t.Helper() - - var s string - require.NoError(t, json.Unmarshal(o.raw(t, key), &s)) - - return s -} - -func (o tofuOutputs) num(t *testing.T, key string) float64 { - t.Helper() - - var n float64 - require.NoError(t, json.Unmarshal(o.raw(t, key), &n)) - - return n -} - -func (o tofuOutputs) strSlice(t *testing.T, key string) []string { - t.Helper() - - var s []string - require.NoError(t, json.Unmarshal(o.raw(t, key), &s)) - - return s -} - -// jsonEq asserts the output decodes equal to want (handles bools/strings/numbers). -func (o tofuOutputs) jsonEq(t *testing.T, key string, want any) { - t.Helper() - - wantJSON, err := json.Marshal(want) - require.NoError(t, err) - require.JSONEq(t, string(wantJSON), string(o.raw(t, key))) -} diff --git a/hscontrol/servertest/assertions.go b/hscontrol/servertest/assertions.go index 54da6473d..2dcfab3d9 100644 --- a/hscontrol/servertest/assertions.go +++ b/hscontrol/servertest/assertions.go @@ -1,7 +1,9 @@ package servertest import ( + "net/netip" "testing" + "time" ) // AssertMeshComplete verifies that every client in the slice sees @@ -65,6 +67,72 @@ func AssertPeerOnline(tb testing.TB, observer *TestClient, peerName string) { } } +// AssertPeerOffline checks that the observer sees peerName as offline. +func AssertPeerOffline(tb testing.TB, observer *TestClient, peerName string) { + tb.Helper() + + peer, ok := observer.PeerByName(peerName) + if !ok { + // Peer gone entirely counts as "offline" for this assertion. + return + } + + isOnline, known := peer.Online().GetOk() + if known && isOnline { + tb.Errorf("AssertPeerOffline: %s sees peer %s as online, want offline", + observer.Name, peerName) + } +} + +// AssertPeerGone checks that the observer does NOT have peerName in +// its peer list at all. +func AssertPeerGone(tb testing.TB, observer *TestClient, peerName string) { + tb.Helper() + + _, ok := observer.PeerByName(peerName) + if ok { + tb.Errorf("AssertPeerGone: %s still sees peer %s", observer.Name, peerName) + } +} + +// AssertPeerHasAllowedIPs checks that a peer has the expected +// AllowedIPs prefixes. +func AssertPeerHasAllowedIPs(tb testing.TB, observer *TestClient, peerName string, want []netip.Prefix) { + tb.Helper() + + peer, ok := observer.PeerByName(peerName) + if !ok { + tb.Errorf("AssertPeerHasAllowedIPs: %s does not see peer %s", observer.Name, peerName) + + return + } + + got := make([]netip.Prefix, 0, peer.AllowedIPs().Len()) + for i := range peer.AllowedIPs().Len() { + got = append(got, peer.AllowedIPs().At(i)) + } + + if len(got) != len(want) { + tb.Errorf("AssertPeerHasAllowedIPs: %s sees %s with AllowedIPs %v, want %v", + observer.Name, peerName, got, want) + + return + } + + // Build a set for comparison. + wantSet := make(map[netip.Prefix]bool, len(want)) + for _, p := range want { + wantSet[p] = true + } + + for _, p := range got { + if !wantSet[p] { + tb.Errorf("AssertPeerHasAllowedIPs: %s sees %s with unexpected AllowedIP %v (want %v)", + observer.Name, peerName, p, want) + } + } +} + // AssertConsistentState checks that all clients agree on peer // properties: every connected client should see the same set of // peer hostnames. @@ -142,3 +210,54 @@ func AssertSelfHasAddresses(tb testing.TB, client *TestClient) { tb.Errorf("AssertSelfHasAddresses: %s self node has no addresses", client.Name) } } + +// EventuallyAssertMeshComplete retries AssertMeshComplete up to +// timeout, useful when waiting for state to propagate. +func EventuallyAssertMeshComplete(tb testing.TB, clients []*TestClient, timeout time.Duration) { + tb.Helper() + + expected := len(clients) - 1 + deadline := time.After(timeout) + + for { + allGood := true + + for _, c := range clients { + nm := c.Netmap() + if nm == nil || len(nm.Peers) < expected { + allGood = false + + break + } + } + + if allGood { + // Final strict check. + AssertMeshComplete(tb, clients) + + return + } + + select { + case <-deadline: + // Report the failure with details. + for _, c := range clients { + nm := c.Netmap() + + got := 0 + if nm != nil { + got = len(nm.Peers) + } + + if got != expected { + tb.Errorf("EventuallyAssertMeshComplete: %s has %d peers, want %d (timeout %v)", + c.Name, got, expected, timeout) + } + } + + return + case <-time.After(100 * time.Millisecond): + // Poll again. + } + } +} diff --git a/hscontrol/servertest/client.go b/hscontrol/servertest/client.go index 67232dd33..3ff73cf3e 100644 --- a/hscontrol/servertest/client.go +++ b/hscontrol/servertest/client.go @@ -19,8 +19,8 @@ import ( "tailscale.com/util/eventbus" ) -// TestClient wraps a Tailscale [controlclient.Direct] connected to a -// [TestServer]. It tracks all received [netmap.NetworkMap] updates, providing +// TestClient wraps a Tailscale controlclient.Direct connected to a +// TestServer. It tracks all received NetworkMap updates, providing // helpers to wait for convergence and inspect the client's view of // the network. type TestClient struct { @@ -37,13 +37,13 @@ type TestClient struct { pollCancel context.CancelFunc pollDone chan struct{} - // Accumulated state from [tailcfg.MapResponse] callbacks. + // Accumulated state from MapResponse callbacks. mu sync.RWMutex netmap *netmap.NetworkMap history []*netmap.NetworkMap // updates is a buffered channel that receives a signal - // each time a new [netmap.NetworkMap] arrives. + // each time a new NetworkMap arrives. updates chan *netmap.NetworkMap bus *eventbus.Bus @@ -51,7 +51,7 @@ type TestClient struct { tracker *health.Tracker } -// ClientOption configures a [TestClient]. +// ClientOption configures a TestClient. type ClientOption func(*clientConfig) type clientConfig struct { @@ -66,7 +66,7 @@ func WithEphemeral() ClientOption { return func(c *clientConfig) { c.ephemeral = true } } -// WithHostname sets the client's hostname in [tailcfg.Hostinfo]. +// WithHostname sets the client's hostname in Hostinfo. func WithHostname(name string) ClientOption { return func(c *clientConfig) { c.hostname = name } } @@ -82,7 +82,7 @@ func WithUser(user *types.User) ClientOption { return func(c *clientConfig) { c.user = user } } -// NewClient creates a [TestClient], registers it with the [TestServer] +// NewClient creates a TestClient, registers it with the TestServer // using a pre-auth key, and starts long-polling for map updates. func NewClient(tb testing.TB, server *TestServer, name string, opts ...ClientOption) *TestClient { tb.Helper() @@ -171,7 +171,7 @@ func NewClient(tb testing.TB, server *TestServer, name string, opts ...ClientOpt return tc } -// register performs the initial [controlclient.Direct.TryLogin] to register the client. +// register performs the initial TryLogin to register the client. func (c *TestClient) register(tb testing.TB) { tb.Helper() @@ -188,46 +188,23 @@ func (c *TestClient) register(tb testing.TB) { } } -// startPoll begins the long-poll [tailcfg.MapRequest] loop. +// startPoll begins the long-poll MapRequest loop. func (c *TestClient) startPoll(tb testing.TB) { tb.Helper() - c.startPollLoop() -} - -// startPollLoop creates a fresh poll context and launches the background -// [controlclient.Direct.PollNetMap] goroutine, which blocks until the -// context is cancelled or the server closes the connection. -func (c *TestClient) startPollLoop() { c.pollCtx, c.pollCancel = context.WithCancel(context.Background()) c.pollDone = make(chan struct{}) go func() { defer close(c.pollDone) - + // PollNetMap blocks until ctx is cancelled or the server closes + // the connection. _ = c.direct.PollNetMap(c.pollCtx, c) }() } -// resetNetmapState clears the cached netmap and drains any pending -// updates from a previous session so that convergence waits observe -// only the new session's maps. -func (c *TestClient) resetNetmapState() { - c.mu.Lock() - c.netmap = nil - c.mu.Unlock() - - for { - select { - case <-c.updates: - default: - return - } - } -} - -// UpdateFullNetmap implements [controlclient.NetmapUpdater]. -// Called by [controlclient.Direct] when a new [netmap.NetworkMap] is received. +// UpdateFullNetmap implements controlclient.NetmapUpdater. +// Called by controlclient.Direct when a new NetworkMap is received. func (c *TestClient) UpdateFullNetmap(nm *netmap.NetworkMap) { c.mu.Lock() c.netmap = nm @@ -282,7 +259,7 @@ func (c *TestClient) Disconnect(tb testing.TB) { } // Reconnect registers and starts a new long-poll session. -// Call [TestClient.Disconnect] first, or this will disconnect automatically. +// Call Disconnect first, or this will disconnect automatically. func (c *TestClient) Reconnect(tb testing.TB) { tb.Helper() @@ -297,11 +274,24 @@ func (c *TestClient) Reconnect(tb testing.TB) { } } - // Clear stale netmap data and drain pending updates so that callers - // like [TestClient.WaitForPeers] actually wait for the new session's - // map instead of returning immediately based on the old session's - // cached state. - c.resetNetmapState() + // Clear stale netmap data so that callers like WaitForPeers + // actually wait for the new session's map instead of returning + // immediately based on the old session's cached state. + c.mu.Lock() + c.netmap = nil + c.mu.Unlock() + + // Drain any pending updates from the old session so they + // don't satisfy a subsequent WaitForPeers/WaitForUpdate. + for { + select { + case <-c.updates: + default: + goto drained + } + } + +drained: // Re-register and start polling again. c.register(tb) @@ -309,79 +299,6 @@ func (c *TestClient) Reconnect(tb testing.TB) { c.startPoll(tb) } -// LogoutAndDisconnect sends a logout [tailcfg.RegisterRequest] (expiry in -// the past) and tears down the long-poll session, mirroring what -// tailscaled does on `tailscale logout`. The server marks the node -// expired; the poll teardown then triggers the server's disconnect -// grace period, after which the node goes offline. -// -// Safe to call from non-test goroutines: errors are returned, not -// fataled, so many clients can log out concurrently. -func (c *TestClient) LogoutAndDisconnect(ctx context.Context) error { - err := c.direct.TryLogout(ctx) - if err != nil { - return fmt.Errorf("servertest: TryLogout(%s): %w", c.Name, err) - } - - if c.pollCancel != nil { - c.pollCancel() - - select { - case <-c.pollDone: - case <-ctx.Done(): - return fmt.Errorf("servertest: LogoutAndDisconnect(%s): poll did not exit: %w", c.Name, ctx.Err()) - } - } - - return nil -} - -// ReloginAndPoll logs the client back in after [TestClient.LogoutAndDisconnect] -// and starts a fresh long-poll session. [controlclient.Direct.TryLogout] cleared -// the persisted node key, so this generates a new NodeKey and re-registers with -// the same pre-auth key and machine key — the same shape as a real client -// running `tailscale up --authkey=...` after a logout. -// -// Safe to call from non-test goroutines. -func (c *TestClient) ReloginAndPoll(ctx context.Context) error { - url, err := c.direct.TryLogin(ctx, controlclient.LoginDefault) - if err != nil { - return fmt.Errorf("servertest: TryLogin(%s): %w", c.Name, err) - } - - if url != "" { - return fmt.Errorf("servertest: TryLogin(%s): unexpected auth URL %q (expected auto-auth with preauth key)", c.Name, url) //nolint:err113 - } - - c.resetNetmapState() - c.startPollLoop() - - return nil -} - -// RestartPoll tears down the current long-poll session and immediately -// starts a new one without re-registering, the way tailscaled restarts -// its map poll on state-machine transitions (pause/unpause around -// login). The node key is unchanged; the server sees a rapid -// disconnect/reconnect. -// -// Safe to call from non-test goroutines. -func (c *TestClient) RestartPoll(ctx context.Context) error { - if c.pollCancel != nil { - c.pollCancel() - - select { - case <-c.pollDone: - case <-ctx.Done(): - return fmt.Errorf("servertest: RestartPoll(%s): old poll did not exit: %w", c.Name, ctx.Err()) - } - } - - c.startPollLoop() - - return nil -} - // ReconnectAfter disconnects, waits for d, then reconnects. // The timer works correctly with testing/synctest for // time-controlled tests. @@ -398,7 +315,7 @@ func (c *TestClient) ReconnectAfter(tb testing.TB, d time.Duration) { // --- State accessors --- -// Netmap returns the latest [netmap.NetworkMap], or nil if none received yet. +// Netmap returns the latest NetworkMap, or nil if none received yet. func (c *TestClient) Netmap() *netmap.NetworkMap { c.mu.RLock() defer c.mu.RUnlock() @@ -411,25 +328,10 @@ func (c *TestClient) Netmap() *netmap.NetworkMap { func (c *TestClient) WaitForPeers(tb testing.TB, n int, timeout time.Duration) { tb.Helper() - c.waitForPeers(tb, n, timeout, "WaitForPeers", func(got int) bool { return got >= n }) -} - -// waitForPeers blocks until match reports the current peer count -// satisfies the caller's predicate, or until timeout expires. op -// names the caller for the timeout failure message. -func (c *TestClient) waitForPeers( - tb testing.TB, - n int, - timeout time.Duration, - op string, - match func(got int) bool, -) { - tb.Helper() - deadline := time.After(timeout) for { - if nm := c.Netmap(); nm != nil && match(len(nm.Peers)) { + if nm := c.Netmap(); nm != nil && len(nm.Peers) >= n { return } @@ -444,7 +346,7 @@ func (c *TestClient) waitForPeers( got = len(nm.Peers) } - tb.Fatalf("servertest: %s(%s, %d): timeout after %v (got %d peers)", op, c.Name, n, timeout, got) + tb.Fatalf("servertest: WaitForPeers(%s, %d): timeout after %v (got %d peers)", c.Name, n, timeout, got) } } } @@ -523,7 +425,7 @@ func (c *TestClient) UpdateCount() int { return len(c.history) } -// History returns a copy of all [netmap.NetworkMap] snapshots in order. +// History returns a copy of all NetworkMap snapshots in order. func (c *TestClient) History() []*netmap.NetworkMap { c.mu.RLock() defer c.mu.RUnlock() @@ -548,7 +450,27 @@ func (c *TestClient) SelfName() string { func (c *TestClient) WaitForPeerCount(tb testing.TB, n int, timeout time.Duration) { tb.Helper() - c.waitForPeers(tb, n, timeout, "WaitForPeerCount", func(got int) bool { return got == n }) + deadline := time.After(timeout) + + for { + if nm := c.Netmap(); nm != nil && len(nm.Peers) == n { + return + } + + select { + case <-c.updates: + // Check again. + case <-deadline: + nm := c.Netmap() + + got := 0 + if nm != nil { + got = len(nm.Peers) + } + + tb.Fatalf("servertest: WaitForPeerCount(%s, %d): timeout after %v (got %d peers)", c.Name, n, timeout, got) + } + } } // WaitForCondition blocks until condFn returns true on the latest @@ -573,13 +495,13 @@ func (c *TestClient) WaitForCondition(tb testing.TB, desc string, timeout time.D } } -// Direct returns the underlying [controlclient.Direct] for -// advanced operations like [controlclient.Direct.SetHostinfo] or SendUpdate. +// Direct returns the underlying controlclient.Direct for +// advanced operations like SetHostinfo or SendUpdate. func (c *TestClient) Direct() *controlclient.Direct { return c.direct } -// String implements [fmt.Stringer] for debug output. +// String implements fmt.Stringer for debug output. func (c *TestClient) String() string { nm := c.Netmap() if nm == nil { diff --git a/hscontrol/servertest/connect_race_test.go b/hscontrol/servertest/connect_race_test.go index b9110ad92..eebf8aa59 100644 --- a/hscontrol/servertest/connect_race_test.go +++ b/hscontrol/servertest/connect_race_test.go @@ -15,16 +15,16 @@ import ( ) // TestConnectDisconnectRace targets the residual TOCTOU window in -// [state.State.Disconnect]: the connectGeneration check at state.go:644 is not -// atomic with the subsequent [state.NodeStore.UpdateNode] and -// primaryRoutes.SetRoutes calls. A new [state.State.Connect] that runs between the +// state.Disconnect: the connectGeneration check at state.go:644 is not +// atomic with the subsequent NodeStore.UpdateNode and +// primaryRoutes.SetRoutes calls. A new Connect that runs between the // gen check and the mutations can have its effects overwritten by the -// stale [state.State.Disconnect]'s SetRoutes(empty). +// stale Disconnect's SetRoutes(empty). // // The poll.go grace-period flow protects against the most common case -// ([state.State.RemoveNode] + stillConnected). Connect/Disconnect on [state.State] directly +// (RemoveNode + stillConnected). Connect/Disconnect on State directly // bypasses that protection and should still leave the state consistent -// — if it doesn't, that is the bug behind the original race issue. +// — if it doesn't, that is the bug behind issue #3203. // // Run with -race to also catch any data race exposed. func TestConnectDisconnectRace(t *testing.T) { @@ -33,15 +33,15 @@ func TestConnectDisconnectRace(t *testing.T) { route := netip.MustParsePrefix("10.0.0.0/24") - // Use [servertest.NewClient] to get a node fully registered + Connected via the - // real noise/poll path. After this, [state.NodeStore] + primaryRoutes already - // have the node, and [state.State.Connect] has been called once. + // Use NewClient to get a node fully registered + Connected via the + // real noise/poll path. After this, NodeStore + primaryRoutes already + // have the node, and Connect has been called once. // - // Only c2 advertises the route. [tailcfg.NodeView.PrimaryRoutes] preserves a current + // Only c2 advertises the route. PrimaryRoutes preserves a current // primary across changes (anti-flap, see primary.go), so if both // nodes were advertising, c1 (lower NodeID) would stay primary and // the test could never observe the route slipping out of c2's - // [tailcfg.NodeView.PrimaryRoutes] — it would never have been there in the first place. + // PrimaryRoutes — it would never have been there in the first place. c1 := servertest.NewClient(t, srv, "race-r1", servertest.WithUser(user)) c2 := servertest.NewClient(t, srv, "race-r2", servertest.WithUser(user)) @@ -65,19 +65,19 @@ func TestConnectDisconnectRace(t *testing.T) { srv.App.Change(ch) // Wait for advertisement + approval to be reflected as a primary - // route assignment in [tailcfg.NodeView.PrimaryRoutes]; otherwise we'd be racing the - // initial steady-state setup, not the [state.State.Connect]/[state.State.Disconnect] window. + // route assignment in PrimaryRoutes; otherwise we'd be racing the + // initial steady-state setup, not the Connect/Disconnect window. require.Eventually(t, func() bool { return slices.Contains(srv.State().GetNodePrimaryRoutes(r2ID), route) }, 10*time.Second, 50*time.Millisecond, "primary route should be assigned to r2 before driving the race") // Drive the race repeatedly. Each iteration: - // 1. Call [state.State.Connect](id) to obtain a fresh gen — this stands in for + // 1. Call Connect(id) to obtain a fresh gen — this stands in for // a session that "owns" the node. - // 2. Spawn a goroutine that issues [state.State.Disconnect](id, gen) — the + // 2. Spawn a goroutine that issues Disconnect(id, gen) — the // stale deferred disconnect. - // 3. Concurrently spawn a goroutine that issues [state.State.Connect](id) — + // 3. Concurrently spawn a goroutine that issues Connect(id) — // the new session arriving. // 4. After both finish, check the state is consistent: the node // should be online and primaryRoutes should hold the approved diff --git a/hscontrol/servertest/content_test.go b/hscontrol/servertest/content_test.go index 9faeb8e25..144db00ec 100644 --- a/hscontrol/servertest/content_test.go +++ b/hscontrol/servertest/content_test.go @@ -10,7 +10,7 @@ import ( "tailscale.com/types/netmap" ) -// TestContentVerification exercises the correctness of [tailcfg.MapResponse] +// TestContentVerification exercises the correctness of MapResponse // content: that the self node, peers, DERP map, and other fields // are populated correctly. func TestContentVerification(t *testing.T) { diff --git a/hscontrol/servertest/ephemeral_test.go b/hscontrol/servertest/ephemeral_test.go index bccb3f34f..e65cd1d0e 100644 --- a/hscontrol/servertest/ephemeral_test.go +++ b/hscontrol/servertest/ephemeral_test.go @@ -23,7 +23,7 @@ import ( // channel is never closed (documented as a v3 TODO upstream). // - https://github.com/hashicorp/golang-lru/blob/v2.0.7/expirable/expirable_lru.go#L78-L81 // -// 2. database/sql internal goroutines: Uses [sync.RWMutex] which is not +// 2. database/sql internal goroutines: Uses sync.RWMutex which is not // durably blocking in synctest, causing hangs. // - https://github.com/golang/go/issues/77687 (mutex as durably blocking) // @@ -77,7 +77,7 @@ func TestEphemeralNodes(t *testing.T) { // Ensure the ephemeral node's long-poll session is fully // established on the server before disconnecting. Without - // this, the [TestClient.Disconnect] may cancel a [controlclient.Direct.PollNetMap] that hasn't + // this, the Disconnect may cancel a PollNetMap that hasn't // yet reached serveLongPoll, so no grace period or ephemeral // GC would ever be scheduled. ephemeral.WaitForPeers(t, 1, 10*time.Second) diff --git a/hscontrol/servertest/grants_test.go b/hscontrol/servertest/grants_test.go index 63820bce3..d6ac7df4d 100644 --- a/hscontrol/servertest/grants_test.go +++ b/hscontrol/servertest/grants_test.go @@ -16,7 +16,7 @@ import ( // TestGrantPolicies verifies that grant-based policies propagate // correctly through the full control plane (policy -> state -> mapper) -// and produce the expected packet filter rules in client [netmap.NetworkMap]s. +// and produce the expected packet filter rules in client netmaps. func TestGrantPolicies(t *testing.T) { //nolint:gocyclo t.Parallel() @@ -66,7 +66,7 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo return c2.UpdateCount() > countC2 }) - // Verify [netmap.NetworkMap.PacketFilter] is populated with real rules from the grant. + // Verify PacketFilter is populated with real rules from the grant. nm1 := c1.Netmap() require.NotNil(t, nm1) assert.NotNil(t, nm1.PacketFilter, @@ -132,7 +132,7 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo srv.App.Change(changes...) } - // Wait for [netmap.NetworkMap.PacketFilter] with cap match rules to arrive. + // Wait for PacketFilter with cap match rules to arrive. c1.WaitForCondition(t, "packet filter with cap grants", 10*time.Second, func(nm *netmap.NetworkMap) bool { @@ -143,7 +143,7 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo nm1 := c1.Netmap() require.NotNil(t, nm1) - // Check that the packet filter has [filtertype.CapMatch] entries. + // Check that the packet filter has CapMatch entries. // The main grant produces cap/drive and cap/relay. // Companion caps (drive-sharer and relay-target) are // generated with reversed direction. @@ -657,9 +657,9 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo } // TestGrantViaSubnetFilterRules verifies that routers with via grants -// receive [netmap.NetworkMap.PacketFilter] rules that allow the steered subnet traffic. +// receive PacketFilter rules that allow the steered subnet traffic. // This is a regression test: without per-node filter compilation for -// via grants, the router's [netmap.NetworkMap.PacketFilter] would lack rules for the +// via grants, the router's PacketFilter would lack rules for the // via-steered subnet destinations, causing traffic to be dropped. func TestGrantViaSubnetFilterRules(t *testing.T) { t.Parallel() @@ -748,7 +748,7 @@ func TestGrantViaSubnetFilterRules(t *testing.T) { return false }) - // Critical: the router's [netmap.NetworkMap.PacketFilter] MUST contain rules with + // Critical: the router's PacketFilter MUST contain rules with // the via-steered subnet (10.0.0.0/24) as a destination. // Without this, the router drops traffic forwarded through it. routerNM := routerA.Netmap() @@ -772,123 +772,6 @@ func TestGrantViaSubnetFilterRules(t *testing.T) { "without per-node filter compilation for via grants, these rules are missing") } -// TestGrantViaSubnetBroaderDstFilterRules verifies that a via grant -// whose destination is broader than the router's advertised subnet -// still produces a filter rule on the router and surfaces the route in -// the client's AllowedIPs. Reproduces juanfont/headscale#3267 at the -// server level. Pre-fix the policy compiler required exact prefix -// equality, so the router's PacketFilter had no rule for the steered -// destination and traffic was silently dropped. -func TestGrantViaSubnetBroaderDstFilterRules(t *testing.T) { - t.Parallel() - - srv := servertest.NewServer(t) - routerUser := srv.CreateUser(t, "rt-user") - clientUser := srv.CreateUser(t, "cl-user") - - advertised := netip.MustParsePrefix("10.33.0.0/16") - broaderDst := netip.MustParsePrefix("10.0.0.0/8") - - changed, err := srv.State().SetPolicy([]byte(`{ - "tagOwners": { - "tag:router-a": ["rt-user@"], - "tag:group-a": ["cl-user@"] - }, - "grants": [ - { - "src": ["tag:router-a", "tag:group-a"], - "dst": ["tag:router-a", "tag:group-a"], - "ip": ["*"] - }, - { - "src": ["tag:group-a"], - "dst": ["10.0.0.0/8"], - "ip": ["*"], - "via": ["tag:router-a"] - } - ], - "autoApprovers": { - "routes": { - "10.33.0.0/16": ["tag:router-a"] - } - } - }`)) - require.NoError(t, err) - - if changed { - changes, err := srv.State().ReloadPolicy() - require.NoError(t, err) - srv.App.Change(changes...) - } - - routerA := servertest.NewClient(t, srv, "router-a", - servertest.WithUser(routerUser), - servertest.WithTags("tag:router-a")) - clientA := servertest.NewClient(t, srv, "client-a", - servertest.WithUser(clientUser), - servertest.WithTags("tag:group-a")) - - routerA.WaitForPeers(t, 1, 15*time.Second) - clientA.WaitForPeers(t, 1, 15*time.Second) - - routerA.Direct().SetHostinfo(&tailcfg.Hostinfo{ - BackendLogID: "servertest-router-a", - Hostname: "router-a", - RoutableIPs: []netip.Prefix{advertised}, - }) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - _ = routerA.Direct().SendUpdate(ctx) - - routerAID := findNodeID(t, srv, "router-a") - _, routeChange, err := srv.State().SetApprovedRoutes( - routerAID, []netip.Prefix{advertised}) - require.NoError(t, err) - srv.App.Change(routeChange) - - // Client should see the advertised route in router's AllowedIPs. - clientA.WaitForCondition(t, "clientA sees advertised route via router-a", - 15*time.Second, - func(nm *netmap.NetworkMap) bool { - for _, p := range nm.Peers { - hi := p.Hostinfo() - if hi.Valid() && hi.Hostname() == "router-a" { - for i := range p.AllowedIPs().Len() { - if p.AllowedIPs().At(i) == advertised { - return true - } - } - } - } - - return false - }) - - // Router's PacketFilter must contain the broader grant dst as a - // destination — that is what Tailscale SaaS emits for via grants. - routerNM := routerA.Netmap() - require.NotNil(t, routerNM) - require.NotNil(t, routerNM.PacketFilter, - "router PacketFilter should not be nil") - - var foundBroaderDst bool - - for _, m := range routerNM.PacketFilter { - for _, dst := range m.Dsts { - dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits()) - if dstPrefix == broaderDst { - foundBroaderDst = true - } - } - } - - assert.True(t, foundBroaderDst, - "router PacketFilter should contain destination rules for the broader grant dst 10.0.0.0/8; "+ - "the via gate requires advertised-route overlap, and the emitted prefix is the dst") -} - // TestGrantViaExitNodeNoFilterRules verifies wire-format SaaS compat: // the exit node's PacketFilter must not contain literal 0.0.0.0/0 or // ::/0 destinations. Internally, autogroup:internet via grants emit a @@ -1101,7 +984,7 @@ func hasCapMatches(matches []filtertype.Match) bool { return false } -// hasDstRules returns true if any [filtertype.Match] in the slice contains a +// hasDstRules returns true if any Match in the slice contains a // non-empty Dsts list. func hasDstRules(matches []filtertype.Match) bool { for _, m := range matches { diff --git a/hscontrol/servertest/ha_dynamic_test.go b/hscontrol/servertest/ha_dynamic_test.go index 475b34dca..86a223a6b 100644 --- a/hscontrol/servertest/ha_dynamic_test.go +++ b/hscontrol/servertest/ha_dynamic_test.go @@ -13,13 +13,13 @@ import ( ) // Dynamic HA failover scenarios, observed from a viewer client's -// perspective. Unlike the static [TestViaGrantHACompat] golden tests, +// perspective. Unlike the static TestViaGrantHACompat golden tests, // these exercise runtime transitions: a primary going unhealthy, // revoking its approved route, or losing its tag, and verify that -// the viewer's [netmap.NetworkMap] converges to the new primary. These are the +// the viewer's netmap converges to the new primary. These are the // end-to-end signals that static captures cannot cover. -// hasPeerPrimaryRoute reports whether the viewer's current [netmap.NetworkMap] +// hasPeerPrimaryRoute reports whether the viewer's current netmap // lists route as a PrimaryRoute on the peer with the given hostname. func hasPeerPrimaryRoute(nm *netmap.NetworkMap, peerHost string, route netip.Prefix) bool { if nm == nil { @@ -43,7 +43,7 @@ func hasPeerPrimaryRoute(nm *netmap.NetworkMap, peerHost string, route netip.Pre } // TestHAFailover_ViewerSeesPrimaryFlip verifies that when an HA -// primary is marked unhealthy, the viewer's [netmap.NetworkMap] flips the route's +// primary is marked unhealthy, the viewer's netmap flips the route's // primary assignment from the old primary to the standby. func TestHAFailover_ViewerSeesPrimaryFlip(t *testing.T) { t.Parallel() @@ -72,7 +72,7 @@ func TestHAFailover_ViewerSeesPrimaryFlip(t *testing.T) { return hasPeerPrimaryRoute(nm, "dyn-flip-r1", route) }) - changed := srv.State().SetNodeHealth(id1, false) + changed := srv.State().SetNodeUnhealthy(id1, true) require.True(t, changed, "marking primary unhealthy should change primaries") srv.App.Change(change.PolicyChange()) @@ -90,7 +90,7 @@ func TestHAFailover_ViewerSeesPrimaryFlip(t *testing.T) { } // TestHAFailover_ViewerSeesRouteRevoke verifies that when the primary -// revokes its approved route, the viewer's [netmap.NetworkMap] re-elects the +// revokes its approved route, the viewer's netmap re-elects the // standby and the old primary no longer advertises the route. func TestHAFailover_ViewerSeesRouteRevoke(t *testing.T) { t.Parallel() diff --git a/hscontrol/servertest/ha_health_test.go b/hscontrol/servertest/ha_health_test.go index c1904724b..b09ed7d38 100644 --- a/hscontrol/servertest/ha_health_test.go +++ b/hscontrol/servertest/ha_health_test.go @@ -15,7 +15,7 @@ import ( "tailscale.com/tailcfg" ) -// advertiseAndApproveRoute sets [tailcfg.Hostinfo.RoutableIPs] on a client and approves +// advertiseAndApproveRoute sets RoutableIPs on a client and approves // the route on the server. Returns the node ID. func advertiseAndApproveRoute( t *testing.T, @@ -87,7 +87,7 @@ func TestHAHealthProbe_HealthyNodes(t *testing.T) { } // TestHAHealthProbe_UnhealthyFailover verifies that marking a primary -// node unhealthy via the [state.State.SetNodeUnhealthy] API triggers failover to the +// node unhealthy via the PrimaryRoutes API triggers failover to the // standby. func TestHAHealthProbe_UnhealthyFailover(t *testing.T) { t.Parallel() @@ -111,7 +111,7 @@ func TestHAHealthProbe_UnhealthyFailover(t *testing.T) { require.Contains(t, primaries, route, "node 1 should be primary initially") // Mark node 1 unhealthy — should failover to node 2. - changed := srv.State().SetNodeHealth(nodeID1, false) + changed := srv.State().SetNodeUnhealthy(nodeID1, true) assert.True(t, changed, "marking primary unhealthy should change primaries") primaries2 := srv.State().GetNodePrimaryRoutes(nodeID2) @@ -141,12 +141,12 @@ func TestHAHealthProbe_RecoveryNoFlap(t *testing.T) { nodeID2 := advertiseAndApproveRoute(t, srv, c2, route) // Failover: node 1 → node 2. - srv.State().SetNodeHealth(nodeID1, false) + srv.State().SetNodeUnhealthy(nodeID1, true) primaries := srv.State().GetNodePrimaryRoutes(nodeID2) require.Contains(t, primaries, route, "node 2 should be primary") // Recovery: node 1 healthy again. Node 2 should STAY primary. - changed := srv.State().SetNodeHealth(nodeID1, true) + changed := srv.State().SetNodeUnhealthy(nodeID1, false) assert.False(t, changed, "recovery should not change primaries (no flap)") primaries = srv.State().GetNodePrimaryRoutes(nodeID2) @@ -173,10 +173,10 @@ func TestHAHealthProbe_ConnectClearsUnhealthy(t *testing.T) { advertiseAndApproveRoute(t, srv, c2, route) // Mark unhealthy. - srv.State().SetNodeHealth(nodeID1, false) + srv.State().SetNodeUnhealthy(nodeID1, true) assert.False(t, srv.State().IsNodeHealthy(nodeID1)) - // Reconnect clears unhealthy via [state.State.Connect] → [state.State.ClearUnhealthy]. + // Reconnect clears unhealthy via State.Connect → ClearUnhealthy. c1.Disconnect(t) c1.Reconnect(t) @@ -190,7 +190,7 @@ func TestHAHealthProbe_ConnectClearsUnhealthy(t *testing.T) { // that clearing a node's approved routes also clears any stale // Unhealthy bit, mirroring the legacy routes.SetRoutes(empty) // auto-clear. Without this, a probe timeout that lands just before -// [state.State.SetApprovedRoutes] would surface as a stale unhealthy node forever. +// SetApprovedRoutes would surface as a stale unhealthy node forever. func TestHAHealthProbe_SetApprovedRoutesEmptyClearsUnhealthy(t *testing.T) { t.Parallel() @@ -208,7 +208,7 @@ func TestHAHealthProbe_SetApprovedRoutesEmptyClearsUnhealthy(t *testing.T) { nodeID1 := advertiseAndApproveRoute(t, srv, c1, route) advertiseAndApproveRoute(t, srv, c2, route) - srv.State().SetNodeHealth(nodeID1, false) + srv.State().SetNodeUnhealthy(nodeID1, true) require.False(t, srv.State().IsNodeHealthy(nodeID1)) _, _, err := srv.State().SetApprovedRoutes(nodeID1, nil) @@ -223,7 +223,7 @@ func TestHAHealthProbe_SetApprovedRoutesEmptyClearsUnhealthy(t *testing.T) { // HA candidate; carrying the bit forward leaks into DebugRoutes. // // The poll handler waits a 10s grace period before calling -// [state.State.Disconnect], so the assertion is wrapped in Eventually with a +// state.Disconnect, so the assertion is wrapped in Eventually with a // generous timeout. func TestHAHealthProbe_DisconnectClearsUnhealthy(t *testing.T) { t.Parallel() @@ -242,7 +242,7 @@ func TestHAHealthProbe_DisconnectClearsUnhealthy(t *testing.T) { nodeID1 := advertiseAndApproveRoute(t, srv, c1, route) advertiseAndApproveRoute(t, srv, c2, route) - srv.State().SetNodeHealth(nodeID1, false) + srv.State().SetNodeUnhealthy(nodeID1, true) require.False(t, srv.State().IsNodeHealthy(nodeID1)) c1.Disconnect(t) @@ -255,7 +255,7 @@ func TestHAHealthProbe_DisconnectClearsUnhealthy(t *testing.T) { // TestHAHealthProbe_SetUnhealthyNoRoutesIsNoOp verifies the // defensive guard for the still-online-but-no-routes case: a probe -// that fires after [state.State.SetApprovedRoutes](empty) should not be allowed +// that fires after SetApprovedRoutes(empty) should not be allowed // to install a stale Unhealthy bit either. func TestHAHealthProbe_SetUnhealthyNoRoutesIsNoOp(t *testing.T) { t.Parallel() @@ -277,81 +277,10 @@ func TestHAHealthProbe_SetUnhealthyNoRoutesIsNoOp(t *testing.T) { _, _, err := srv.State().SetApprovedRoutes(nodeID1, nil) require.NoError(t, err) - srv.State().SetNodeHealth(nodeID1, false) + srv.State().SetNodeUnhealthy(nodeID1, true) assert.True(t, srv.State().IsNodeHealthy(nodeID1), - "SetNodeHealth(false) on node with no approved routes should be a no-op") -} - -// TestHAHealthProbe_ReconnectDuringProbeKeepsHealthy reproduces the -// race that surfaced as a TestHASubnetRouterFailover flake: a probe -// dispatched against the previous poll session sees the timeout fire -// while the client is briefly disconnected. With the session guard in -// [HAHealthProber.ProbeOnce], the timeout path observes the reconnect -// and bails out instead of installing a spurious Unhealthy bit. -// -// Without the guard, the primary fails over to the standby and the -// anti-flap election preserves that choice even after the original -// primary is fully back online. -func TestHAHealthProbe_ReconnectDuringProbeKeepsHealthy(t *testing.T) { - t.Parallel() - - srv := servertest.NewServer(t) - user := srv.CreateUser(t, "ha-probe-reconnect") - - route := netip.MustParsePrefix("10.102.0.0/24") - - c1 := servertest.NewClient(t, srv, "ha-pr-r1", servertest.WithUser(user)) - c2 := servertest.NewClient(t, srv, "ha-pr-r2", servertest.WithUser(user)) - - c1.WaitForPeers(t, 1, 10*time.Second) - c2.WaitForPeers(t, 1, 10*time.Second) - - nodeID1 := advertiseAndApproveRoute(t, srv, c1, route) - advertiseAndApproveRoute(t, srv, c2, route) - - // Node 1 is primary (lowest ID, healthy). - require.Contains(t, - srv.State().GetNodePrimaryRoutes(nodeID1), route, - "node 1 should be primary initially") - - prober := state.NewHAHealthProber( - srv.State(), - types.HARouteConfig{ - ProbeInterval: 30 * time.Second, - ProbeTimeout: 2 * time.Second, - }, - srv.URL, - srv.App.MapBatcher().IsConnected, - ) - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - // TestClient does not implement ping responses, so every probe - // times out. We exploit that to observe the timeout path under a - // reconnect race: kick a probe in a goroutine, bounce the - // primary's poll session, and confirm the prober drops the stale - // timeout instead of marking the node unhealthy. - done := make(chan struct{}) - - go func() { - defer close(done) - - prober.ProbeOnce(ctx, srv.App.Change) - }() - - c1.Disconnect(t) - c1.Reconnect(t) - c1.WaitForPeers(t, 1, 10*time.Second) - - <-done - - assert.True(t, srv.State().IsNodeHealthy(nodeID1), - "reconnect during probe must not flip node unhealthy") - assert.Contains(t, - srv.State().GetNodePrimaryRoutes(nodeID1), route, - "node 1 should remain primary after stale-probe timeout") + "SetNodeUnhealthy on node with no approved routes should be a no-op") } // TestHAHealthProbe_NoHARoutes verifies that the prober is a no-op diff --git a/hscontrol/servertest/ha_property_test.go b/hscontrol/servertest/ha_property_test.go deleted file mode 100644 index 565e36af2..000000000 --- a/hscontrol/servertest/ha_property_test.go +++ /dev/null @@ -1,642 +0,0 @@ -package servertest_test - -import ( - "context" - "fmt" - "net/netip" - "slices" - "sync" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/juanfont/headscale/hscontrol/state" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" - "tailscale.com/tailcfg" -) - -// haPropClient bundles a TestClient with the node ID and route it -// advertises. The property test mutates connection state through -// these handles and inspects the resulting NodeStore snapshot. -type haPropClient struct { - tc *servertest.TestClient - id types.NodeID - name string - route netip.Prefix - // connected mirrors the prober's view (mapBatcher.IsConnected). - // Tracked alongside the runtime read so anti-flap invariants can - // reason about the candidate set the prober actually evaluates. - connected bool - // freshSinceReconnect is true between a successful Reconnect and - // the prober's second observation of the new SessionEpoch — the - // window in which the session-stability guard must defer instead - // of installing an Unhealthy bit. Reset by the first ProberTick - // that runs against the new session; cleared on Disconnect. - freshSinceReconnect bool -} - -// checkTB embeds *testing.T to satisfy testing.TB (which requires an -// unexported method only the testing package can provide) while -// collecting Cleanup functions in a local LIFO stack. runCleanups() -// runs them when the rapid check body exits so each check's resources -// are released immediately, not at the outer test's teardown — which -// would otherwise accumulate hundreds of servers and tens of thousands -// of goroutines across a 300-check run. -// -// Fatal/Fatalf still call through to *testing.T and fail the outer -// test. Inside a rapid check use rt.Fatalf instead so rapid can shrink -// the failing op sequence. -type checkTB struct { - *testing.T - - mu sync.Mutex - cleanups []func() -} - -func (c *checkTB) Cleanup(fn func()) { - c.mu.Lock() - defer c.mu.Unlock() - - c.cleanups = append(c.cleanups, fn) -} - -func (c *checkTB) runCleanups() { - c.mu.Lock() - cs := c.cleanups - c.cleanups = nil - c.mu.Unlock() - - for _, v := range slices.Backward(cs) { - v() - } -} - -// haReadvertise pushes hostinfo + approved routes again. Called after -// every Reconnect so the new poll session re-publishes the prefix. The -// controlclient.Direct carries the SetHostinfo state across -// re-registration, but pushing again removes a race where the test -// inspects PrimaryRoutes before the initial map of the new session -// landed. -func haReadvertise( - tb testing.TB, - srv *servertest.TestServer, - c *haPropClient, -) { - tb.Helper() - - c.tc.Direct().SetHostinfo(&tailcfg.Hostinfo{ - BackendLogID: "servertest-" + c.name, - Hostname: c.name, - RoutableIPs: []netip.Prefix{c.route}, - }) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - _ = c.tc.Direct().SendUpdate(ctx) - - _, ch, err := srv.State().SetApprovedRoutes(c.id, []netip.Prefix{c.route}) - require.NoError(tb, err) - srv.App.Change(ch) -} - -// readPrimaries builds the prefix→owner map by inverting -// State.GetNodePrimaryRoutes for every client in the fleet. The State -// API does not expose the raw snapshot map directly; iterating the -// known clients reconstructs it without poking at NodeStore internals. -// Any inconsistency between routes and isPrimaryRoute surfaces as a -// duplicate ownership claim and trips invariant 4. -func readPrimaries( - rt *rapid.T, - srv *servertest.TestServer, - clients []*haPropClient, -) map[netip.Prefix]types.NodeID { - rt.Helper() - - out := make(map[netip.Prefix]types.NodeID) - - for _, c := range clients { - for _, p := range srv.State().GetNodePrimaryRoutes(c.id) { - if prev, ok := out[p]; ok { - rt.Fatalf( - "prefix %s has two owners: %d and %d "+ - "(GetNodePrimaryRoutes claimed both)", - p, prev, c.id, - ) - } - - out[p] = c.id - } - } - - return out -} - -// checkHAInvariants walks every prefix → primary mapping in the live -// snapshot and asserts the six properties documented in the test -// header. prevPrimaries is the snapshot taken before the just-applied -// op so anti-flap can compare moves. -// -//nolint:gocyclo // invariant checker over several independent properties -func checkHAInvariants( - rt *rapid.T, - srv *servertest.TestServer, - clients []*haPropClient, - prevPrimaries map[netip.Prefix]types.NodeID, - skipAntiFlap bool, -) { - rt.Helper() - - st := srv.State() - primaries := readPrimaries(rt, srv, clients) - - // Collect every prefix advertised by the fleet so the all-down - // honesty check can iterate them, even prefixes with zero current - // advertisers. - prefixSet := make(map[netip.Prefix]struct{}) - for _, c := range clients { - prefixSet[c.route] = struct{}{} - } - - // Build the candidate set per prefix from the NodeStore: a node is a - // candidate when it is IsOnline=true and AllApprovedRoutes contains - // the prefix. This is exactly the input to electPrimaryRoutes; the - // prober's mapBatcher.IsConnected view is a separate gate the - // prober consults before dispatching a probe, not part of the - // election input. - advertisersByPrefix := make(map[netip.Prefix][]types.NodeID) - - for _, c := range clients { - nv, ok := st.GetNodeByID(c.id) - if !ok || !nv.Valid() { - continue - } - - online, known := nv.IsOnline().GetOk() - if !known || !online { - continue - } - - for _, p := range nv.AllApprovedRoutes() { - if p == c.route { - advertisersByPrefix[p] = append(advertisersByPrefix[p], c.id) - } - } - } - - // Invariant 4: isPrimaryRoute must be consistent with routes. - for prefix, owner := range primaries { - got := srv.State().GetNodePrimaryRoutes(owner) - if !slices.Contains(got, prefix) { - rt.Fatalf( - "prefix %s primary=%d but GetNodePrimaryRoutes(%d)=%v missing it", - prefix, owner, owner, got, - ) - } - } - - for prefix, owner := range primaries { - nv, ok := st.GetNodeByID(owner) - if !ok || !nv.Valid() { - rt.Fatalf( - "prefix %s primary %d missing from NodeStore", - prefix, owner, - ) - } - - // Invariant 1: primary IsOnline in the NodeStore view. - // state.Disconnect's 10s grace can leave a recently-disconnected - // node with IsOnline=true; the structural rule still requires - // the bit before the snapshot may keep it as primary. - online, known := nv.IsOnline().GetOk() - if !known || !online { - rt.Fatalf( - "prefix %s primary %d is not online (IsOnline=%v known=%v)", - prefix, owner, online, known, - ) - } - - // Invariant 2: primary advertises the prefix and is among the - // snapshot's candidate set. - approved := nv.AllApprovedRoutes() - if !slices.Contains(approved, prefix) { - rt.Fatalf( - "prefix %s primary %d does not advertise it (approved=%v)", - prefix, owner, approved, - ) - } - - if !slices.Contains(advertisersByPrefix[prefix], owner) { - rt.Fatalf( - "prefix %s primary %d not in advertiser set %v", - prefix, owner, advertisersByPrefix[prefix], - ) - } - - // Invariant 5 (anti-flap): if the previous snapshot already had - // a primary for this prefix AND that primary is still a healthy, - // online, advertising candidate, the new snapshot must keep it. - // Skipped on settle reads where we re-check without an op in - // between — the prober may run in a deferred batch and the - // election may legitimately move once on its own timeline. - if !skipAntiFlap { - if prev, hadPrev := prevPrimaries[prefix]; hadPrev && prev != owner { - prevNV, exists := st.GetNodeByID(prev) - if exists && prevNV.Valid() { - prevOnline, prevKnown := prevNV.IsOnline().GetOk() - prevApproved := prevNV.AllApprovedRoutes() - - stillCandidate := prevKnown && prevOnline && - slices.Contains(prevApproved, prefix) - stillHealthy := st.IsNodeHealthy(prev) - - if stillCandidate && stillHealthy { - rt.Fatalf( - "prefix %s flapped primary %d -> %d "+ - "while previous primary was still a healthy candidate", - prefix, prev, owner, - ) - } - } - } - } - } - - // Invariant 6 (all-down honesty): if every candidate of a prefix is - // either offline OR unhealthy, the snapshot must either drop the - // primary OR keep one that is still a candidate from the same set - // (the all-unhealthy preserve-prev rule). A primary pointed at a - // non-candidate after every candidate has gone dark would point - // peers at a node the prober has already declared unreachable. - for prefix := range prefixSet { - owner, hasPrimary := primaries[prefix] - if !hasPrimary { - continue - } - - candidates := advertisersByPrefix[prefix] - - // Empty candidates with a primary present trips invariant 2 - // above; reaching this branch with len(candidates) == 0 means - // the snapshot is internally inconsistent. - if len(candidates) == 0 { - rt.Fatalf( - "prefix %s has primary %d but no advertiser in the snapshot", - prefix, owner, - ) - } - - anyHealthy := slices.ContainsFunc(candidates, st.IsNodeHealthy) - - // Healthy preference: a primary should not be unhealthy when a - // healthy candidate exists. Asserting this through the real - // prober → State → NodeStore seam catches any divergence the - // unit-level model would miss. - if anyHealthy && !st.IsNodeHealthy(owner) { - rt.Fatalf( - "prefix %s primary %d unhealthy but %v has a healthy candidate", - prefix, owner, candidates, - ) - } - - // All-unhealthy guards: with every candidate unhealthy, the - // election has exactly two correct choices: - // - preserve prev primary when it is still a candidate - // - leave the prefix unmapped (no candidate fallback) - // - // Any other outcome points peers at a node already declared - // unreachable. - if !anyHealthy { - prevOwner, hadPrev := prevPrimaries[prefix] - prevStillCandidate := hadPrev && - slices.Contains(candidates, prevOwner) - - if prevStillCandidate && owner != prevOwner { - rt.Fatalf( - "prefix %s all-unhealthy election picked %d, "+ - "but prev primary %d is still a candidate — "+ - "preserve-prev rule violated", - prefix, owner, prevOwner, - ) - } - - if !prevStillCandidate && (!hadPrev || owner != prevOwner) { - rt.Fatalf( - "prefix %s all-unhealthy fallback elected %d "+ - "but prev primary %v is gone; election "+ - "must leave the prefix unmapped", - prefix, owner, prevOwner, - ) - } - } - } -} - -// snapshotPrimaries returns a defensive copy of the live primary -// route map so the caller can compare to a later snapshot without -// aliasing. Inverts GetNodePrimaryRoutes since State does not expose -// the raw snapshot map. -func snapshotPrimaries( - rt *rapid.T, - srv *servertest.TestServer, - clients []*haPropClient, -) map[netip.Prefix]types.NodeID { - return readPrimaries(rt, srv, clients) -} - -// TestHAProberProperty drives a real Headscale TestServer with a small -// fleet of HA-route-advertising clients through a randomised sequence -// of connect / disconnect / reconnect / prober-tick operations and -// asserts that the live PrimaryRoutes() snapshot honours every HA -// invariant after each step. -// -// Coverage versus the unit-level NodeStore property test: -// -// - This test exercises the FULL seam: real prober → real PingNode -// dispatch → real responseCh handling → State.BatchSetNodeHealth -// → NodeStore election → mapper batcher updates. The unit test -// bypasses the prober entirely. -// -// - Disconnect/Reconnect goes through the real poll-session -// lifecycle: mapBatcher.RemoveNode flips IsConnected, the 10s -// grace period defers state.Disconnect, and state.Connect bumps -// SessionEpoch on rejoin. The prober defers fresh-session probes -// so a reconnect mid-cycle does not install a stale Unhealthy. -// -// - The batched BatchSetNodeHealth is observable through the -// all-unhealthy preserve-prev invariant: per-call writes would -// publish an intermediate "one unhealthy, one healthy" snapshot, -// leaving primary on a non-prev node after both flips land. -// -// - The all-unhealthy fallback that leaves prefixes unmapped is -// covered by the honesty invariant: with every candidate -// unhealthy AND prev gone from the candidate set, the prefix -// must stay unmapped instead of pinned to any candidate. -// -// Caveat: TestClient is a real controlclient.Direct that responds to -// PingRequest over Noise, so the timeout path of the prober rarely -// fires. The session-stability guard is asserted -// (freshSinceReconnect tracking + healthy assertion after the first -// probe of a new session) but the tight race — -// probe-against-old-session-while-reconnecting — needs a dedicated -// reconnect test to trigger deterministically. -// -// Ops drawn by rapid: -// -// - ClientDisconnect(i) — cancel poll session, flips IsConnected -// - ClientReconnect(i) — re-register and start new poll session -// - ProberTick() — invoke prober.ProbeOnce synchronously -// - WaitForSnapshot() — re-check invariants without a new op -// -// Initial connect happens once during setup so the fleet always has -// the same baseline. Drawing ClientConnect in the op loop would -// require setting up auth keys and waiting for peer convergence -// inside the rapid body, which would dominate the per-check budget -// for little extra coverage. -func TestHAProberProperty(t *testing.T) { - // Per-check wall-cost is ~15-25s (real Noise handshake on each - // reconnect), so the default 100-check rapid budget blows past - // the 10-minute go test timeout used by the Tests workflow. - // Skip on CI by default so the everyday sweep stays fast; runs - // locally so seed shrinking works without an opt-in flag. - if util.IsCI() { - t.Skip("skipping HA prober property test in CI; runs locally") - } - - if testing.Short() { - t.Skip("skipping property test in short mode") - } - - rapid.Check(t, func(rt *rapid.T) { - // Fresh server per rapid check: rapid shrinks by replaying ops - // from a clean state, so we cannot reuse a server across runs. - // Cleanups registered against checkTB run at the end of THIS - // check so server resources are released before the next one - // starts. - tb := &checkTB{T: t} - defer tb.runCleanups() - - srv := servertest.NewServer(tb) - user := srv.CreateUser(tb, "ha-prop") - - // Three HA candidates is the smallest set that exercises every - // election shape (primary, secondary, tertiary) while keeping - // per-check setup under ~10 seconds. Higher numbers blow out - // the wall-clock budget without adding new failure modes — the - // dual-disconnect and reconnect-during-probe regressions all - // reproduce at N=3. - const numClients = 3 - - route := netip.MustParsePrefix("10.200.0.0/24") - - clients := make([]*haPropClient, 0, numClients) - - for i := range numClients { - name := fmt.Sprintf("ha-prop-r%d", i+1) - tc := servertest.NewClient(tb, srv, name, servertest.WithUser(user)) - - // Wait for at least the initial netmap so the registration - // committed and findNodeID is guaranteed to see the node. - tc.WaitForUpdate(tb, 10*time.Second) - - id := findNodeID(tb, srv, name) - - c := &haPropClient{ - tc: tc, - id: id, - name: name, - route: route, - connected: true, - } - clients = append(clients, c) - - haReadvertise(tb, srv, c) - } - - // Prober uses a short timeout so each ProberTick op drains - // the cycle in well under a second. TestClient is backed by a - // real controlclient.Direct that DOES respond to PingRequest - // over Noise (see TestPingNode), so most probes record a - // successful response. The probe-timeout path still fires for - // nodes whose poll session is mid-bounce — exactly the seam - // the session-stability guard was built for — but timing it - // reliably from outside the prober is hard; the property test - // focuses on the steady-state election invariants instead. - prober := state.NewHAHealthProber( - srv.State(), - types.HARouteConfig{ - ProbeInterval: 30 * time.Second, - ProbeTimeout: 50 * time.Millisecond, - }, - srv.URL, - srv.App.MapBatcher().IsConnected, - ) - - // Sanity: every client should now be an advertiser of route, - // and the snapshot must have an HA primary already. - require.Eventually(tb, func() bool { - for _, c := range clients { - if slices.Contains( - srv.State().GetNodePrimaryRoutes(c.id), - route, - ) { - return true - } - } - - return false - }, 10*time.Second, 50*time.Millisecond, - "setup: route should have a primary before drawing ops") - - idxGen := rapid.IntRange(0, numClients-1) - opGen := rapid.IntRange(0, 3) - opCount := rapid.IntRange(15, 35).Draw(rt, "opCount") - - for step := range opCount { - op := opGen.Draw(rt, fmt.Sprintf("op_%d", step)) - - prev := snapshotPrimaries(rt, srv, clients) - - // Anti-flap is meaningful only across operations that do - // not themselves mutate the candidate set or per-node - // health. Connect/Disconnect both mutate IsOnline (via - // state.Connect, or via the asynchronous grace-period - // state.Disconnect), and the resulting election move is - // expected, not a flap. ProbeOnce, by contrast, must keep - // the primary on the previous owner whenever that owner - // is still a candidate — this is what the reconnect- - // during-probe and dual-disconnect guards preserve. - isProberOp := op == 2 - - switch op { - case 0: // ClientDisconnect - idx := idxGen.Draw(rt, fmt.Sprintf("disc_idx_%d", step)) - c := clients[idx] - - // Refuse to disconnect the last connected node. The - // prober's HANodes gate requires ≥2 online candidates - // per prefix; with only one node left, no probe runs - // and the prober-driven invariants (anti-flap, - // reconnect-defer) become unfalsifiable. Keeping at - // least two candidates is enough to exercise every - // failure shape we care about. - connectedCount := 0 - - for _, cc := range clients { - if cc.connected { - connectedCount++ - } - } - - if c.connected && connectedCount > 2 { - c.tc.Disconnect(tb) - c.connected = false - c.freshSinceReconnect = false - } - - case 1: // ClientReconnect - idx := idxGen.Draw(rt, fmt.Sprintf("rec_idx_%d", step)) - c := clients[idx] - - if !c.connected { - c.tc.Reconnect(tb) - - // Wait for the new poll session to register with - // the batcher so the prober's IsConnected gate - // reflects the reconnect on the next ProberTick. - // WaitForUpdate adds ~1s per call; polling - // IsConnected directly converges in <100ms. - require.Eventually( - tb, - func() bool { - return srv.App.MapBatcher(). - IsConnected(c.id) - }, - 10*time.Second, - 5*time.Millisecond, - "reconnect: batcher should see %s online", - c.name, - ) - - c.connected = true - c.freshSinceReconnect = true - - // Re-push hostinfo so the new session's initial - // map sets RoutableIPs as expected. SetApprovedRoutes - // is idempotent on the same set. - haReadvertise(tb, srv, c) - } - - case 2: // ProberTick — drive one synchronous probe cycle. - // Capture freshness BEFORE running the probe; the - // prober itself flips a node from "fresh" to "stable" - // when it sees the same SessionEpoch twice, and the - // "fresh sessions must defer" rule is expressed - // against the pre-probe state. - freshThisCycle := make(map[types.NodeID]bool, len(clients)) - for _, c := range clients { - freshThisCycle[c.id] = c.freshSinceReconnect - } - - ctx, cancel := context.WithTimeout( - context.Background(), - 5*time.Second, - ) - - // Use App.Change as the dispatcher — the prober batches - // its results through BatchSetNodeHealth and emits a - // single PolicyChange. The real wiring is what we want - // to test. - prober.ProbeOnce(ctx, srv.App.Change) - cancel() - - // Session-stability rule: the first probe against a - // freshly-reconnected node must defer instead of - // installing an Unhealthy bit. In this servertest the - // TestClient is backed by a real controlclient.Direct - // that DOES respond to PingRequests over Noise, so - // the tight timing window — probe dispatched against - // an old session whose ping never returns — rarely - // opens. The rule still runs as a sanity gate: if it - // ever does fire, the freshly-reconnected node will - // surface as unhealthy after a single probe, and the - // test catches it. - for _, c := range clients { - if !freshThisCycle[c.id] || !c.connected { - continue - } - - if !srv.State().IsNodeHealthy(c.id) { - rt.Fatalf( - "node %d (%s) was marked unhealthy by "+ - "the first probe after reconnect; "+ - "session-stability guard should have "+ - "deferred", - c.id, c.name, - ) - } - - c.freshSinceReconnect = false - } - - case 3: // WaitForSnapshot — re-check invariants without - // applying a new op. NodeStore writes are synchronous - // inside UpdateNode/UpdateNodes, so there is nothing - // to wait on; the no-op step still verifies that two - // consecutive reads (one snapshot, the same snapshot) - // stay consistent. This is invariant 3: stable - // conditions = stable primary. We skip the anti-flap - // comparison so a primary that legitimately changed - // in the prior op is not re-flagged here. - checkHAInvariants(rt, srv, clients, prev, true) - - continue - } - - checkHAInvariants(rt, srv, clients, prev, !isProberOp) - } - }) -} diff --git a/hscontrol/servertest/harness.go b/hscontrol/servertest/harness.go index 7461b1cb7..17706cbb5 100644 --- a/hscontrol/servertest/harness.go +++ b/hscontrol/servertest/harness.go @@ -8,7 +8,7 @@ import ( "github.com/juanfont/headscale/hscontrol/types" ) -// TestHarness orchestrates a [TestServer] with multiple [TestClient] instances, +// TestHarness orchestrates a TestServer with multiple TestClients, // providing a convenient setup for multi-node control plane tests. type TestHarness struct { Server *TestServer @@ -18,7 +18,7 @@ type TestHarness struct { defaultUser *types.User } -// HarnessOption configures a [TestHarness]. +// HarnessOption configures a TestHarness. type HarnessOption func(*harnessConfig) type harnessConfig struct { @@ -33,24 +33,24 @@ func defaultHarnessConfig() *harnessConfig { } } -// WithServerOptions passes [ServerOption] values through to the underlying -// [TestServer]. +// WithServerOptions passes ServerOptions through to the underlying +// TestServer. func WithServerOptions(opts ...ServerOption) HarnessOption { return func(c *harnessConfig) { c.serverOpts = append(c.serverOpts, opts...) } } -// WithDefaultClientOptions applies [ClientOption] values to every client -// created by [NewHarness]. +// WithDefaultClientOptions applies ClientOptions to every client +// created by NewHarness. func WithDefaultClientOptions(opts ...ClientOption) HarnessOption { return func(c *harnessConfig) { c.clientOpts = append(c.clientOpts, opts...) } } -// WithConvergenceTimeout sets how long [TestHarness.WaitForMeshComplete] waits. +// WithConvergenceTimeout sets how long WaitForMeshComplete waits. func WithConvergenceTimeout(d time.Duration) HarnessOption { return func(c *harnessConfig) { c.convergenceMax = d } } -// NewHarness creates a [TestServer] and numClients connected clients. +// NewHarness creates a TestServer and numClients connected clients. // All clients share a default user and are registered with reusable // pre-auth keys. The harness waits for all clients to form a // complete mesh before returning. @@ -76,7 +76,8 @@ func NewHarness(tb testing.TB, numClients int, opts ...HarnessOption) *TestHarne for i := range numClients { name := clientName(i) - c := h.newClient(tb, name, hc.clientOpts...) + copts := append([]ClientOption{WithUser(user)}, hc.clientOpts...) + c := NewClient(tb, server, name, copts...) h.clients = append(h.clients, c) } @@ -123,22 +124,13 @@ func (h *TestHarness) AddClient(tb testing.TB, opts ...ClientOption) *TestClient tb.Helper() name := clientName(len(h.clients)) - c := h.newClient(tb, name, opts...) + copts := append([]ClientOption{WithUser(h.defaultUser)}, opts...) + c := NewClient(tb, h.Server, name, copts...) h.clients = append(h.clients, c) return c } -// newClient creates a [TestClient] on the harness server, prepending the -// shared default user so caller options can override it. -func (h *TestHarness) newClient(tb testing.TB, name string, opts ...ClientOption) *TestClient { - tb.Helper() - - copts := append([]ClientOption{WithUser(h.defaultUser)}, opts...) - - return NewClient(tb, h.Server, name, copts...) -} - // WaitForMeshComplete blocks until every connected client sees // (connectedCount - 1) peers. func (h *TestHarness) WaitForMeshComplete(tb testing.TB, timeout time.Duration) { @@ -153,6 +145,13 @@ func (h *TestHarness) WaitForMeshComplete(tb testing.TB, timeout time.Duration) } } +// WaitForConvergence waits until all connected clients have a +// non-nil NetworkMap and their peer counts have stabilised. +func (h *TestHarness) WaitForConvergence(tb testing.TB, timeout time.Duration) { + tb.Helper() + h.WaitForMeshComplete(tb, timeout) +} + // ChangePolicy sets an ACL policy on the server and propagates changes // to all connected nodes. The policy should be a valid HuJSON policy document. func (h *TestHarness) ChangePolicy(tb testing.TB, policy []byte) { diff --git a/hscontrol/servertest/issues_test.go b/hscontrol/servertest/issues_test.go index 4b06f080f..4859f7947 100644 --- a/hscontrol/servertest/issues_test.go +++ b/hscontrol/servertest/issues_test.go @@ -19,14 +19,14 @@ import ( // These tests are intentionally strict about expected behavior. // Failures surface real issues in the control plane. -// TestIssuesMapContent tests issues with [tailcfg.MapResponse] content correctness. +// TestIssuesMapContent tests issues with MapResponse content correctness. func TestIssuesMapContent(t *testing.T) { t.Parallel() // After mesh formation, all peers should have a known Online status. - // The Online field is set when [state.State.Connect] sends a NodeOnline [tailcfg.PeerChange] - // patch. The initial [tailcfg.MapResponse] (from auth handler) may have Online=nil - // because [state.State.Connect] hasn't run yet, so we wait for the status to propagate. + // The Online field is set when Connect() sends a NodeOnline PeerChange + // patch. The initial MapResponse (from auth handler) may have Online=nil + // because Connect() hasn't run yet, so we wait for the status to propagate. t.Run("initial_map_should_include_peer_online_status", func(t *testing.T) { t.Parallel() h := servertest.NewHarness(t, 3) @@ -55,7 +55,7 @@ func TestIssuesMapContent(t *testing.T) { t.Parallel() h := servertest.NewHarness(t, 2) - // The DiscoKey is sent in the first [tailcfg.MapRequest] (not the [tailcfg.RegisterRequest]), + // The DiscoKey is sent in the first MapRequest (not the RegisterRequest), // so it may take an extra map update to propagate to peers. Wait for // the condition rather than checking the initial netmap. h.Client(0).WaitForCondition(t, "peer has non-zero DiscoKey", @@ -92,7 +92,7 @@ func TestIssuesMapContent(t *testing.T) { } }) - // Each peer should have a valid user profile in the [netmap.NetworkMap]. + // Each peer should have a valid user profile in the netmap. t.Run("all_peers_have_user_profiles", func(t *testing.T) { t.Parallel() @@ -174,7 +174,7 @@ func TestIssuesRoutes(t *testing.T) { // Approving a route via API without the node announcing it must NOT // make the route visible in AllowedIPs. Tailscale uses a strict // advertise-then-approve model: routes are only distributed when the - // node advertises them ([tailcfg.Hostinfo.RoutableIPs]) AND they are approved. + // node advertises them (Hostinfo.RoutableIPs) AND they are approved. // An approval without advertisement is a dormant pre-approval that // activates once the node starts advertising. t.Run("approved_route_without_announcement_not_distributed", func(t *testing.T) { @@ -253,7 +253,7 @@ func TestIssuesRoutes(t *testing.T) { }) }) - // [tailcfg.Hostinfo] route advertisement should be stored on server. + // Hostinfo route advertisement should be stored on server. t.Run("hostinfo_route_advertisement_stored_on_server", func(t *testing.T) { t.Parallel() @@ -491,7 +491,7 @@ func TestIssuesServerMutations(t *testing.T) { assert.Len(t, c3.Peers(), 1) }) - // [tailcfg.Hostinfo] changes should propagate to peers. + // Hostinfo changes should propagate to peers. t.Run("hostinfo_changes_propagate_to_peers", func(t *testing.T) { t.Parallel() @@ -530,11 +530,11 @@ func TestIssuesServerMutations(t *testing.T) { }) } -// TestIssuesNodeStoreConsistency tests [state.NodeStore] + DB consistency. +// TestIssuesNodeStoreConsistency tests NodeStore + DB consistency. func TestIssuesNodeStoreConsistency(t *testing.T) { t.Parallel() - // [state.NodeStore] and DB should agree after mutations. + // NodeStore and DB should agree after mutations. t.Run("nodestore_db_consistency_after_operations", func(t *testing.T) { t.Parallel() @@ -563,13 +563,13 @@ func TestIssuesNodeStoreConsistency(t *testing.T) { require.NoError(t, err, "node should be in database") nsRoutes := nsView.ApprovedRoutes().AsSlice() - dbRoutes := dbNode.ApprovedRoutes.List() + dbRoutes := dbNode.ApprovedRoutes assert.Equal(t, nsRoutes, dbRoutes, "NodeStore and DB should agree on approved routes") }) - // After rapid reconnect, [state.NodeStore] should reflect correct state. + // After rapid reconnect, NodeStore should reflect correct state. t.Run("nodestore_correct_after_rapid_reconnect", func(t *testing.T) { t.Parallel() @@ -673,7 +673,7 @@ func TestIssuesGracePeriod(t *testing.T) { // Ensure the ephemeral node's long-poll session is fully // established on the server before disconnecting. Without - // this, the [TestClient.Disconnect] may cancel a [controlclient.Direct.PollNetMap] that hasn't + // this, the Disconnect may cancel a PollNetMap that hasn't // yet reached serveLongPoll, so no grace period or ephemeral // GC would ever be scheduled. ephemeral.WaitForPeers(t, 1, 10*time.Second) diff --git a/hscontrol/servertest/lifecycle_test.go b/hscontrol/servertest/lifecycle_test.go index c1531550c..c7565f398 100644 --- a/hscontrol/servertest/lifecycle_test.go +++ b/hscontrol/servertest/lifecycle_test.go @@ -1,17 +1,12 @@ package servertest_test import ( - "context" "fmt" - "math/rand/v2" - "strings" "testing" "time" "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/juanfont/headscale/hscontrol/types" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "tailscale.com/types/netmap" ) @@ -121,241 +116,3 @@ func TestConnectionLifecycle(t *testing.T) { } }) } - -// TestLogoutReloginAllClientsConverge is an in-process reproduction of the -// flaky integration tests TestAuthKeyLogoutAndReloginSameUser, -// TestAuthWebFlowLogoutAndReloginSameUser and -// TestAuthWebFlowLogoutAndReloginNewUser: a full mesh of clients logs out, -// the server marks every node expired and offline, then all clients log -// back in near-simultaneously with fresh NodeKeys. In the flake, a subset -// of clients never converges — their netmaps stay empty through the whole -// retry window even though the server believes everything is connected. -// -// Each client here is a real [controlclient.Direct], so the client-side -// netmap assembly semantics (full peer list vs. delta, patch handling for -// unknown peers) match the real Tailscale client. -func TestLogoutReloginAllClientsConverge(t *testing.T) { - if testing.Short() { - t.Skip("relogin convergence test includes 10s+ disconnect grace per iteration") - } - - const ( - numClients = 12 - iterations = 4 - // Maximum random delay between the relogins of different - // clients, so registrations and fresh map streams interleave - // the way concurrent `tailscale up` invocations do. - reloginStagger = 500 * time.Millisecond - ) - - // Production tuning: the integration flake happens with the default - // 800ms batch delay (large coalescing windows) and a multi-worker - // batcher, so reproduce with the same knobs. - h := servertest.NewHarness(t, numClients, - servertest.WithServerOptions( - servertest.WithBatchDelay(800*time.Millisecond), - servertest.WithBatcherWorkers(types.DefaultBatcherWorkers()), - ), - servertest.WithConvergenceTimeout(60*time.Second), - ) - - for iteration := range iterations { - t.Logf("iteration %d: logging out all clients", iteration) - logoutAllAndWaitOffline(t, h) - - t.Logf("iteration %d: relogging in all clients", iteration) - - clients := h.Clients() - errs := make(chan error, len(clients)) - - for _, c := range clients { - go func() { - time.Sleep(rand.N(reloginStagger)) //nolint:forbidigo,gosec // intentional jitter so relogins interleave; weak random is fine - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - errs <- c.ReloginAndPoll(ctx) - }() - } - - for range clients { - require.NoError(t, <-errs) - } - - // Every client must converge to the full mesh. A stuck client — - // the flake — sits at zero peers and fails here. - deadline := time.Now().Add(30 * time.Second) - for _, c := range clients { - waitForMeshOrDump(t, clients, c, numClients-1, time.Until(deadline)) - } - } -} - -// TestLogoutReloginWithPollChurn is the same logout/relogin storm as -// [TestLogoutReloginAllClientsConverge], but each client also restarts its -// map poll once or twice shortly after logging back in — without -// re-registering — the way newer tailscaled versions cycle their map -// session around login state transitions. The integration flake hits the -// head and unstable clients, which churn their sessions far more than -// older releases, so the rapid session replacement is the prime suspect. -func TestLogoutReloginWithPollChurn(t *testing.T) { - if testing.Short() { - t.Skip("relogin convergence test includes 10s+ disconnect grace per iteration") - } - - const ( - numClients = 12 - iterations = 4 - reloginStagger = 500 * time.Millisecond - ) - - h := servertest.NewHarness(t, numClients, - servertest.WithServerOptions( - servertest.WithBatchDelay(800*time.Millisecond), - servertest.WithBatcherWorkers(types.DefaultBatcherWorkers()), - ), - servertest.WithConvergenceTimeout(60*time.Second), - ) - - for iteration := range iterations { - t.Logf("iteration %d: logging out all clients", iteration) - logoutAllAndWaitOffline(t, h) - - t.Logf("iteration %d: relogging in all clients with poll churn", iteration) - - clients := h.Clients() - errs := make(chan error, len(clients)) - - for _, c := range clients { - go func() { - time.Sleep(rand.N(reloginStagger)) //nolint:forbidigo,gosec // intentional jitter so relogins interleave; weak random is fine - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - err := c.ReloginAndPoll(ctx) - if err != nil { - errs <- err - return - } - - // Churn the map session like a freshly logged-in - // tailscaled: restart the poll once or twice with - // small random gaps. - for range 1 + rand.IntN(2) { //nolint:gosec // weak random is fine for test jitter - time.Sleep(rand.N(400 * time.Millisecond)) //nolint:forbidigo,gosec // intentional jitter between poll restarts; weak random is fine - - err = c.RestartPoll(ctx) - if err != nil { - errs <- err - return - } - } - - errs <- nil - }() - } - - for range clients { - require.NoError(t, <-errs) - } - - deadline := time.Now().Add(30 * time.Second) - for _, c := range clients { - waitForMeshOrDump(t, clients, c, numClients-1, time.Until(deadline)) - } - } -} - -// logoutAllAndWaitOffline logs every client out concurrently, then blocks -// until the server reports each node expired and offline — the integration -// tests' logout barrier, including the ~10s disconnect grace period. -func logoutAllAndWaitOffline(t *testing.T, h *servertest.TestHarness) { - t.Helper() - - clients := h.Clients() - errs := make(chan error, len(clients)) - - for _, c := range clients { - go func() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - errs <- c.LogoutAndDisconnect(ctx) - }() - } - - for range clients { - require.NoError(t, <-errs) - } - - st := h.Server.State() - - require.EventuallyWithT(t, func(c *assert.CollectT) { - for _, node := range st.ListNodes().All() { - assert.True(c, node.IsExpired(), "node %d should be expired after logout", node.ID()) - online := node.IsOnline() - assert.True(c, online.Valid() && !online.Get(), "node %d should be offline after logout", node.ID()) - } - }, 30*time.Second, 100*time.Millisecond, "all nodes expired and offline after logout") -} - -// waitForMeshOrDump waits until client c reports at least wantPeers peers. -// On timeout it dumps every client's view of the mesh before failing, so a -// reproduced flake shows exactly which clients are stuck and what they see. -func waitForMeshOrDump(t *testing.T, all []*servertest.TestClient, c *servertest.TestClient, wantPeers int, timeout time.Duration) { - t.Helper() - - deadline := time.After(timeout) - ticker := time.NewTicker(100 * time.Millisecond) - - defer ticker.Stop() - - for { - if nm := c.Netmap(); nm != nil && len(nm.Peers) >= wantPeers { - return - } - - select { - case <-ticker.C: - case <-deadline: - for _, other := range all { - t.Logf("client %s netmap: %s", other.Name, describeNetmap(other)) - } - - nm := c.Netmap() - - got := 0 - if nm != nil { - got = len(nm.Peers) - } - - t.Fatalf("client %s did not converge: want %d peers, got %d", c.Name, wantPeers, got) - } - } -} - -// describeNetmap renders a client's current netmap as a compact string for -// failure dumps: peer names with their expiry/online flags. -func describeNetmap(c *servertest.TestClient) string { - nm := c.Netmap() - if nm == nil { - return "" - } - - var out strings.Builder - - fmt.Fprintf(&out, "%d peers:", len(nm.Peers)) - - for _, p := range nm.Peers { - hostname := "" - if hi := p.Hostinfo(); hi.Valid() { - hostname = hi.Hostname() - } - - fmt.Fprintf(&out, " %s(id=%d expired=%t online=%v)", hostname, p.ID(), p.KeyExpiry().Before(time.Now()), p.Online()) - } - - return out.String() -} diff --git a/hscontrol/servertest/ping_test.go b/hscontrol/servertest/ping_test.go index eba4add67..3ab5dc041 100644 --- a/hscontrol/servertest/ping_test.go +++ b/hscontrol/servertest/ping_test.go @@ -15,7 +15,7 @@ import ( ) // TestPingNode verifies the full ping round-trip: the server sends a -// [tailcfg.PingRequest] via [tailcfg.MapResponse], the real [controlclient.Direct] handles it +// PingRequest via MapResponse, the real controlclient.Direct handles it // by making a HEAD request back over Noise, and the ping tracker records // the latency. func TestPingNode(t *testing.T) { @@ -105,7 +105,7 @@ func TestPingTwoSameNode(t *testing.T) { require.NotEqual(t, pingID1, pingID2) - // Send both [tailcfg.PingRequest]s. + // Send both PingRequests. url1 := h.Server.URL + "/machine/ping-response?id=" + pingID1 url2 := h.Server.URL + "/machine/ping-response?id=" + pingID2 @@ -136,7 +136,7 @@ func TestPingTwoSameNode(t *testing.T) { } } -// TestPingResolveByHostname verifies that [state.State.ResolveNode] can find a node +// TestPingResolveByHostname verifies that ResolveNode can find a node // by hostname and that the resolved node can be pinged. func TestPingResolveByHostname(t *testing.T) { t.Parallel() diff --git a/hscontrol/servertest/policy_test.go b/hscontrol/servertest/policy_test.go index 5bc6bc4a3..a6952bc76 100644 --- a/hscontrol/servertest/policy_test.go +++ b/hscontrol/servertest/policy_test.go @@ -156,9 +156,9 @@ func TestPolicyChanges(t *testing.T) { // (Prefix, Host) resolve to exactly the literal prefix and do NOT expand // to include the matching node's other IP addresses. // -// [netmap.NetworkMap.PacketFilter] rules are INBOUND: they tell the destination node what +// PacketFilter rules are INBOUND: they tell the destination node what // traffic to accept. So the IPv6 destination rule appears in test2's -// [netmap.NetworkMap.PacketFilter] (the destination), not test1's (the source). +// PacketFilter (the destination), not test1's (the source). func TestIPv6OnlyPrefixACL(t *testing.T) { t.Parallel() @@ -193,7 +193,7 @@ func TestIPv6OnlyPrefixACL(t *testing.T) { c1.WaitForPeers(t, 1, 10*time.Second) c2.WaitForPeers(t, 1, 10*time.Second) - // [netmap.NetworkMap.PacketFilter] is an INBOUND filter: test2 (the destination) should + // PacketFilter is an INBOUND filter: test2 (the destination) should // have the rule allowing traffic FROM test1's IPv6. nm2 := c2.Netmap() require.NotNil(t, nm2) diff --git a/hscontrol/servertest/poll_race_test.go b/hscontrol/servertest/poll_race_test.go index aea59e5ce..500bf3fbe 100644 --- a/hscontrol/servertest/poll_race_test.go +++ b/hscontrol/servertest/poll_race_test.go @@ -13,7 +13,7 @@ import ( ) // TestPollRace targets logical race conditions specifically in the -// poll.go session lifecycle and the [mapper.Batcher]'s handling of concurrent +// poll.go session lifecycle and the batcher's handling of concurrent // sessions for the same node. func TestPollRace(t *testing.T) { @@ -22,9 +22,9 @@ func TestPollRace(t *testing.T) { // The core race: when a node disconnects, poll.go starts a // grace period goroutine (10s ticker loop). If the node // reconnects during this period, the new session calls - // [state.State.Connect] to mark the node online. But the old grace period - // goroutine is still running and may call [state.State.Disconnect] AFTER - // the new [state.State.Connect], setting IsOnline=false incorrectly. + // Connect() to mark the node online. But the old grace period + // goroutine is still running and may call Disconnect() AFTER + // the new Connect(), setting IsOnline=false incorrectly. // // This test verifies the exact symptom: after reconnect within // the grace period, the server-side node state should be online. @@ -99,7 +99,7 @@ func TestPollRace(t *testing.T) { // Wait the full grace period (10s) after reconnect. The old // grace period goroutine should have checked IsConnected // and found the node connected, so should NOT have called - // [state.State.Disconnect]. + // Disconnect(). t.Run("server_state_online_12s_after_reconnect", func(t *testing.T) { t.Parallel() @@ -195,8 +195,8 @@ func TestPollRace(t *testing.T) { } }) - // The [mapper.Batcher]'s IsConnected check: when the grace period - // goroutine calls IsConnected, it should return true if + // The batcher's IsConnected check: when the grace period + // goroutine calls IsConnected(), it should return true if // a new session has been added for the same node. t.Run("batcher_knows_reconnected_during_grace", func(t *testing.T) { t.Parallel() diff --git a/hscontrol/servertest/race_test.go b/hscontrol/servertest/race_test.go index bf6e6f0e9..81f30f230 100644 --- a/hscontrol/servertest/race_test.go +++ b/hscontrol/servertest/race_test.go @@ -19,7 +19,7 @@ import ( // TestRace contains tests designed to trigger race conditions in // the control plane. Run with -race to detect data races. // These tests stress concurrent access patterns in poll.go, -// the [mapper.Batcher], the [state.NodeStore], and the [mapper] subsystem. +// the batcher, the NodeStore, and the mapper. // TestRacePollSessionReplacement tests the race between an old // poll session's deferred cleanup and a new session starting. @@ -28,8 +28,8 @@ func TestRacePollSessionReplacement(t *testing.T) { // Rapidly replace the poll session by doing immediate // disconnect+reconnect. This races the old session's - // deferred cleanup ([state.NodeStore.RemoveNode], [state.State.Disconnect], grace period - // goroutine) with the new session's setup ([state.NodeStore.AddNode], [state.State.Connect], + // deferred cleanup (RemoveNode, Disconnect, grace period + // goroutine) with the new session's setup (AddNode, Connect, // initial map send). t.Run("immediate_session_replace_10x", func(t *testing.T) { t.Parallel() @@ -393,13 +393,13 @@ func TestRaceConnectDuringGracePeriod(t *testing.T) { }) } -// TestRaceBatcherContention tests race conditions in the [mapper.Batcher] +// TestRaceBatcherContention tests race conditions in the batcher // when many changes arrive simultaneously. func TestRaceBatcherContention(t *testing.T) { t.Parallel() // Many nodes connecting at the same time generates many - // concurrent [hscontrol.Headscale.Change] calls. The [mapper.Batcher] must handle this + // concurrent Change() calls. The batcher must handle this // without dropping updates or panicking. t.Run("many_simultaneous_connects", func(t *testing.T) { t.Parallel() @@ -427,8 +427,8 @@ func TestRaceBatcherContention(t *testing.T) { }) // Rapid connect + disconnect + connect of different nodes - // generates interleaved [state.NodeStore.AddNode]/[state.NodeStore.RemoveNode]/[state.NodeStore.AddNode] in the - // [mapper.Batcher]. + // generates interleaved AddNode/RemoveNode/AddNode in the + // batcher. t.Run("interleaved_add_remove_add", func(t *testing.T) { t.Parallel() @@ -514,7 +514,7 @@ func TestRaceBatcherContention(t *testing.T) { } // TestRaceMapResponseDuringDisconnect tests what happens when a -// [tailcfg.MapResponse] is being written while the session is being torn down. +// map response is being written while the session is being torn down. func TestRaceMapResponseDuringDisconnect(t *testing.T) { t.Parallel() @@ -587,12 +587,12 @@ func TestRaceMapResponseDuringDisconnect(t *testing.T) { }) } -// TestRaceNodeStoreContention tests concurrent access to the [state.NodeStore]. +// TestRaceNodeStoreContention tests concurrent access to the NodeStore. func TestRaceNodeStoreContention(t *testing.T) { t.Parallel() - // Many [state.State.GetNodeByID] calls while nodes are connecting and - // disconnecting. This tests the [state.NodeStore]'s read/write locking. + // Many GetNodeByID calls while nodes are connecting and + // disconnecting. This tests the NodeStore's read/write locking. t.Run("concurrent_reads_during_mutations", func(t *testing.T) { t.Parallel() @@ -655,7 +655,7 @@ func TestRaceNodeStoreContention(t *testing.T) { } }) - // [state.State.ListNodes] while nodes are being added and removed. + // ListNodes while nodes are being added and removed. t.Run("list_nodes_during_churn", func(t *testing.T) { t.Parallel() diff --git a/hscontrol/servertest/routes_test.go b/hscontrol/servertest/routes_test.go index 4f224192e..85f818206 100644 --- a/hscontrol/servertest/routes_test.go +++ b/hscontrol/servertest/routes_test.go @@ -17,8 +17,6 @@ import ( // TestRoutes verifies that route advertisements and approvals // propagate correctly through the control plane to all peers. -// -//nolint:gocyclo // table-driven test driver with many independent subtests func TestRoutes(t *testing.T) { t.Parallel() @@ -73,14 +71,14 @@ func TestRoutes(t *testing.T) { RoutableIPs: []netip.Prefix{routePrefix}, }) - // Send a non-streaming update to push the new [tailcfg.Hostinfo]. + // Send a non-streaming update to push the new hostinfo. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = c1.Direct().SendUpdate(ctx) // The observer should eventually see the advertised routes - // in the peer's [tailcfg.Hostinfo]. + // in the peer's hostinfo. c2.WaitForCondition(t, "advertised route in hostinfo", 15*time.Second, func(nm *netmap.NetworkMap) bool { @@ -115,7 +113,7 @@ func TestRoutes(t *testing.T) { c1.WaitForPeers(t, 1, 10*time.Second) c2.WaitForPeers(t, 1, 10*time.Second) - // Step 1: Advertise the route by updating [tailcfg.Hostinfo]. + // Step 1: Advertise the route by updating hostinfo. c1.Direct().SetHostinfo(&tailcfg.Hostinfo{ BackendLogID: "servertest-fullrt-advertiser", Hostname: "fullrt-advertiser", @@ -127,7 +125,7 @@ func TestRoutes(t *testing.T) { _ = c1.Direct().SendUpdate(ctx) - // Wait for the server to process the [tailcfg.Hostinfo] update + // Wait for the server to process the hostinfo update // by waiting for observer to see the advertised route. c2.WaitForCondition(t, "hostinfo update propagated", 10*time.Second, @@ -216,18 +214,18 @@ func TestRoutes(t *testing.T) { } }) - // Reproduces the HA secondary recovery race: + // Reproduces https://github.com/juanfont/headscale/issues/3203: // HA tracking loses the secondary subnet router after all routers serving // the route have been offline simultaneously and one of them returns. // // Two assertions split the failure surface: // R1 — server-side primary route state restores after reconnect. - // R2 — observer's [netmap.NetworkMap] shows the reconnected router online with + // R2 — observer's netmap shows the reconnected router online with // the route in its primary set. - // If R1 fails the bug is in [state.State.Connect] / primaryRoutes; if R1 passes - // and R2 fails the bug is in change broadcast / [mapper.Batcher]. + // If R1 fails the bug is in state.Connect / primaryRoutes; if R1 passes + // and R2 fails the bug is in change broadcast / mapBatcher. // - // Caveat: [TestClient.Reconnect] re-registers via [controlclient.Direct.TryLogin] in addition + // Caveat: servertest's Reconnect re-registers via TryLogin in addition // to starting a new poll session. Production reconnects after a brief // network outage may bypass re-registration. If this test passes on // main, fall back to the integration variant noted in the plan @@ -249,7 +247,7 @@ func TestRoutes(t *testing.T) { obs.WaitForPeers(t, 2, 10*time.Second) - // Both routers advertise the same route via their [tailcfg.Hostinfo]. + // Both routers advertise the same route via their hostinfo. advertise := func(c *servertest.TestClient, name string) { t.Helper() c.Direct().SetHostinfo(&tailcfg.Hostinfo{ @@ -307,8 +305,8 @@ func TestRoutes(t *testing.T) { // 3. Reconnect r2 (cable plugged back in). r2.Reconnect(t) - // [tailcfg.Hostinfo] is part of the [controlclient.Direct] state; the [TestClient.Reconnect] - // helper re-registers via [controlclient.Direct.TryLogin] which carries the same [tailcfg.Hostinfo] + // Hostinfo is part of the controlclient.Direct state; the Reconnect + // helper re-registers via TryLogin which carries the same Hostinfo // that was set above. Push it again to be sure the announced route // is registered in the new session. advertise(r2, "ha3203-router2") @@ -345,4 +343,4 @@ func TestRoutes(t *testing.T) { }) } -// [findNodeID] is defined in issues_test.go. +// findNodeID is defined in issues_test.go. diff --git a/hscontrol/servertest/server.go b/hscontrol/servertest/server.go index 9601694cc..c50b1da4a 100644 --- a/hscontrol/servertest/server.go +++ b/hscontrol/servertest/server.go @@ -1,11 +1,10 @@ // Package servertest provides an in-process test harness for Headscale's // control plane. It wires a real Headscale server to real Tailscale -// [controlclient.Direct] instances, enabling fast, deterministic tests +// controlclient.Direct instances, enabling fast, deterministic tests // of the full control protocol without Docker or separate processes. package servertest import ( - "context" "net" "net/http" "net/netip" @@ -20,7 +19,7 @@ import ( ) // TestServer is an in-process Headscale control server suitable for -// use with Tailscale's [controlclient.Direct]. +// use with Tailscale's controlclient.Direct. // // Networking uses tailscale.com/net/memnet so that all TCP // connections stay in-process — no real sockets are opened. @@ -34,7 +33,7 @@ type TestServer struct { st *state.State } -// ServerOption configures a [TestServer]. +// ServerOption configures a TestServer. type ServerOption func(*serverConfig) type serverConfig struct { @@ -44,7 +43,6 @@ type serverConfig struct { nodeExpiry time.Duration batcherWorkers int taildropEnabled bool - realListener bool } func defaultServerConfig() *serverConfig { @@ -67,13 +65,6 @@ func WithBufferedChanSize(n int) ServerOption { return func(c *serverConfig) { c.bufferedChanSize = n } } -// WithBatcherWorkers sets the number of batcher worker goroutines. -// Defaults to 1 for deterministic tests; pass -// [types.DefaultBatcherWorkers] to match production concurrency. -func WithBatcherWorkers(n int) ServerOption { - return func(c *serverConfig) { c.batcherWorkers = n } -} - // WithEphemeralTimeout sets the ephemeral node inactivity timeout. func WithEphemeralTimeout(d time.Duration) ServerOption { return func(c *serverConfig) { c.ephemeralTimeout = d } @@ -84,14 +75,6 @@ func WithNodeExpiry(d time.Duration) ServerOption { return func(c *serverConfig) { c.nodeExpiry = d } } -// WithRealListener binds the HTTP API to a real loopback TCP port instead of -// the in-process memnet, so external processes — the Tailscale SDK over a real -// socket, tscli, OpenTofu — can reach the API. The Noise/[TestClient] control -// path still uses memnet, so this is for REST/API tests only. -func WithRealListener() ServerOption { - return func(c *serverConfig) { c.realListener = true } -} - // WithTaildropEnabled toggles the Taildrop file-sharing feature. // Defaults to true to match production. Pass false to verify // behaviour when an operator has switched the toggle off — e.g. @@ -174,24 +157,13 @@ func NewServer(tb testing.TB, opts ...ServerOption) *TestServer { app.StartBatcherForTest(tb) app.StartEphemeralGCForTest(tb) - // Start the HTTP server. By default it binds an in-memory network so all - // TCP connections stay in-process; WithRealListener swaps in a real loopback - // port so external binaries can connect. - var ( - memNetwork memnet.Network - ln net.Listener - ) - - if sc.realListener { - var lc net.ListenConfig - - ln, err = lc.Listen(context.Background(), "tcp", "127.0.0.1:0") - } else { - ln, err = memNetwork.Listen("tcp", "127.0.0.1:443") - } + // Start the HTTP server over an in-memory network so that all + // TCP connections stay in-process. + var memNetwork memnet.Network + ln, err := memNetwork.Listen("tcp", "127.0.0.1:443") if err != nil { - tb.Fatalf("servertest: Listen: %v", err) + tb.Fatalf("servertest: memnet Listen: %v", err) } httpServer := &http.Server{ @@ -229,15 +201,15 @@ func (s *TestServer) State() *state.State { // Close shuts down the in-memory HTTP server and listener. // Subsystem cleanup (batcher, ephemeral GC) is handled by -// [testing.TB.Cleanup] callbacks registered in [hscontrol.Headscale.StartBatcherForTest] and -// [hscontrol.Headscale.StartEphemeralGCForTest]. +// tb.Cleanup callbacks registered in StartBatcherForTest and +// StartEphemeralGCForTest. func (s *TestServer) Close() { s.httpServer.Close() s.ln.Close() } // MemNet returns the in-memory network used by this server, -// so that [TestClient] dialers can be wired to it. +// so that TestClient dialers can be wired to it. func (s *TestServer) MemNet() *memnet.Network { return s.memNet } @@ -254,71 +226,43 @@ func (s *TestServer) CreateUser(tb testing.TB, name string) *types.User { return u } -// CreateAPIKey mints an API key string (the Bearer/Basic credential). When -// owner is non-nil the key is owned by that user, so the v2 API mints -// user-owned (untagged) auth keys on its behalf. -func (s *TestServer) CreateAPIKey(tb testing.TB, owner *types.User) string { - tb.Helper() - - keyStr, key, err := s.st.CreateAPIKey(nil) - if err != nil { - tb.Fatalf("servertest: CreateAPIKey: %v", err) - } - - if owner != nil { - err := s.st.SetAPIKeyUser(key.ID, types.UserID(owner.ID)) - if err != nil { - tb.Fatalf("servertest: SetAPIKeyUser: %v", err) - } - } - - return keyStr -} - -// CreateRegisteredNode mints a registered node (allocated IPs, registered -// method) in BOTH the database and the in-memory NodeStore, then returns its -// view. The v2 device endpoints resolve nodes via State.GetNodeByID, which reads -// the NodeStore, so a DB-only node would be invisible to them. -func (s *TestServer) CreateRegisteredNode(tb testing.TB, owner *types.User, hostname ...string) types.NodeView { - tb.Helper() - - node := s.st.CreateRegisteredNodeForTest(owner, hostname...) - // CreateRegisteredNodeForTest sets UserID but leaves the User association - // unloaded; the device response renders the owner login, so attach it. - node.User = owner - - return s.st.PutNodeInStoreForTest(*node) -} - // CreatePreAuthKey creates a reusable pre-auth key for the given user. func (s *TestServer) CreatePreAuthKey(tb testing.TB, userID types.UserID) string { tb.Helper() - return s.createPreAuthKey(tb, userID, true, false, nil) + + uid := userID + + pak, err := s.st.CreatePreAuthKey(&uid, true, false, nil, nil) + if err != nil { + tb.Fatalf("servertest: CreatePreAuthKey: %v", err) + } + + return pak.Key } // CreateTaggedPreAuthKey creates a reusable pre-auth key with ACL tags. func (s *TestServer) CreateTaggedPreAuthKey(tb testing.TB, userID types.UserID, tags []string) string { tb.Helper() - return s.createPreAuthKey(tb, userID, true, false, tags) + + uid := userID + + pak, err := s.st.CreatePreAuthKey(&uid, true, false, nil, tags) + if err != nil { + tb.Fatalf("servertest: CreateTaggedPreAuthKey: %v", err) + } + + return pak.Key } // CreateEphemeralPreAuthKey creates an ephemeral pre-auth key. func (s *TestServer) CreateEphemeralPreAuthKey(tb testing.TB, userID types.UserID) string { tb.Helper() - return s.createPreAuthKey(tb, userID, false, true, nil) -} -func (s *TestServer) createPreAuthKey( - tb testing.TB, - userID types.UserID, - reusable, ephemeral bool, - tags []string, -) string { - tb.Helper() + uid := userID - pak, err := s.st.CreatePreAuthKey(&userID, reusable, ephemeral, nil, tags) + pak, err := s.st.CreatePreAuthKey(&uid, false, true, nil, nil) if err != nil { - tb.Fatalf("servertest: createPreAuthKey: %v", err) + tb.Fatalf("servertest: CreateEphemeralPreAuthKey: %v", err) } return pak.Key diff --git a/hscontrol/servertest/sshcheck_test.go b/hscontrol/servertest/sshcheck_test.go deleted file mode 100644 index 4ff1ed317..000000000 --- a/hscontrol/servertest/sshcheck_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package servertest_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/servertest" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// TestSSHCheckReDelegatesWhenSessionMissing exercises the fix for -// https://github.com/juanfont/headscale/issues/3305 with a real control -// client. The dst node runs the SSH-check poll over its actual Noise -// connection: it first obtains a genuine HoldAndDelegate auth_id, that auth -// session is then dropped from the cache (as it would be on expiry, eviction, -// or a control-plane restart), and the follow-up poll for the now-missing -// session must re-delegate a fresh HoldAndDelegate rather than dead-ending the -// client with an error it keeps retrying until the SSH connection times out. -func TestSSHCheckReDelegatesWhenSessionMissing(t *testing.T) { - t.Parallel() - - h := servertest.NewHarness(t, 2) - - srcID := types.NodeID(h.Client(0).Netmap().SelfNode.ID()) //nolint:gosec - dstID := types.NodeID(h.Client(1).Netmap().SelfNode.ID()) //nolint:gosec - - // Subject the same-user (src, dst) pair to an SSH check. - h.ChangePolicy(t, []byte(`{ - "ssh": [{ - "action": "check", - "src": ["harness-default@"], - "dst": ["autogroup:self"], - "users": ["autogroup:nonroot"] - }] - }`)) - - // Sanity: the policy must actually subject this pair to a check, otherwise - // the test would pass for the wrong reason. - _, checkFound := h.Server.State().SSHCheckParams(srcID, dstID) - require.True(t, checkFound, "test setup: (src, dst) must be subject to an SSH check") - - // The dst node's first poll yields a real HoldAndDelegate carrying a real, - // cached auth_id — nothing is fabricated. - initial := pollSSHAction(t, h.Server.URL, h.Client(1), srcID, dstID, "") - require.NotEmpty(t, initial.HoldAndDelegate, "initial poll must hold and delegate, got %+v", initial) - - authID := authIDFromHoldURL(t, initial.HoldAndDelegate) - _, ok := h.Server.State().GetAuthCacheEntry(authID) - require.True(t, ok, "the auth session must be cached after the initial poll") - - // Drop the session, reproducing a natural loss (expiry/eviction/restart). - h.Server.State().DeleteAuthCacheEntryForTest(authID) - _, ok = h.Server.State().GetAuthCacheEntry(authID) - require.False(t, ok, "the auth session must be gone before the follow-up poll") - - // The follow-up poll carries the real auth_id whose session is now missing. - // With an active check the server must re-delegate a fresh session. - followUp := pollSSHAction(t, h.Server.URL, h.Client(1), srcID, dstID, authID.String()) - require.NotEmpty(t, followUp.HoldAndDelegate, - "a missing session under an active check must re-delegate, got %+v", followUp) - - require.NotEqual(t, authID, authIDFromHoldURL(t, followUp.HoldAndDelegate), - "re-delegation must mint a fresh auth_id") -} - -// pollSSHAction issues an /machine/ssh/action poll from the given node over its -// real Noise connection, as tailscaled does. An empty authID is the initial -// poll; a non-empty one is a follow-up. -func pollSSHAction( - t *testing.T, - serverURL string, - node *servertest.TestClient, - srcID, dstID types.NodeID, - authID string, -) tailcfg.SSHAction { - t.Helper() - - actionURL := fmt.Sprintf("%s/machine/ssh/action/%d/to/%d", serverURL, srcID, dstID) - if authID != "" { - actionURL += "?auth_id=" + authID - } - - // Noise requests are addressed with the https scheme; the control client - // routes them over the established Noise connection (mirroring how - // controlclient issues its own register/map calls). - actionURL = strings.Replace(actionURL, "http://", "https://", 1) - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, actionURL, nil) - require.NoError(t, err) - - resp, err := node.Direct().DoNoiseRequest(req) - require.NoError(t, err) - - defer resp.Body.Close() - - require.Equal(t, http.StatusOK, resp.StatusCode, "ssh action poll must return 200") - - var action tailcfg.SSHAction - require.NoError(t, json.NewDecoder(resp.Body).Decode(&action)) - - return action -} - -// authIDFromHoldURL extracts the auth_id query parameter from a HoldAndDelegate -// URL. -func authIDFromHoldURL(t *testing.T, holdURL string) types.AuthID { - t.Helper() - - u, err := url.Parse(holdURL) - require.NoError(t, err) - - authID, err := types.AuthIDFromString(u.Query().Get("auth_id")) - require.NoError(t, err, "HoldAndDelegate URL missing a valid auth_id: %s", holdURL) - - return authID -} diff --git a/hscontrol/servertest/stress_test.go b/hscontrol/servertest/stress_test.go index e330aa3d8..ad22a3ac2 100644 --- a/hscontrol/servertest/stress_test.go +++ b/hscontrol/servertest/stress_test.go @@ -20,7 +20,7 @@ import ( // consistency bugs. // TestStressConnectDisconnect exercises rapid connect/disconnect -// patterns that stress the grace period, batcher, and [state.NodeStore]. +// patterns that stress the grace period, batcher, and NodeStore. func TestStressConnectDisconnect(t *testing.T) { t.Parallel() @@ -536,7 +536,7 @@ func TestStressDataIntegrity(t *testing.T) { } }) - // [netmap.NetworkMap.MachineKey] should be consistent: the server should track + // MachineKey should be consistent: the server should track // the same machine key the client registered with. t.Run("machine_key_consistent", func(t *testing.T) { t.Parallel() @@ -551,7 +551,7 @@ func TestStressDataIntegrity(t *testing.T) { nm := c1.Netmap() require.NotNil(t, nm) - // The client's [netmap.NetworkMap.MachineKey] should be non-zero. + // The client's MachineKey in the netmap should be non-zero. assert.False(t, nm.MachineKey.IsZero(), "client's MachineKey should be non-zero") @@ -564,7 +564,7 @@ func TestStressDataIntegrity(t *testing.T) { "client and server should agree on MachineKey") }) - // [netmap.NetworkMap.NodeKey] should be consistent between client and server. + // NodeKey should be consistent between client and server. t.Run("node_key_consistent", func(t *testing.T) { t.Parallel() diff --git a/hscontrol/servertest/via_compat_test.go b/hscontrol/servertest/via_compat_test.go index 859e5fd4d..b87f520fa 100644 --- a/hscontrol/servertest/via_compat_test.go +++ b/hscontrol/servertest/via_compat_test.go @@ -38,7 +38,7 @@ var viaCompatTests = []struct { } // TestViaGrantMapCompat loads golden captures from Tailscale SaaS and -// compares headscale's [tailcfg.MapResponse] structure against the captured [netmap.NetworkMap]. +// compares headscale's MapResponse structure against the captured netmap. // // The comparison is IP-independent: it validates peer visibility, route // prefixes in AllowedIPs, and PrimaryRoutes — not literal Tailscale IP @@ -128,7 +128,7 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) { // Determine which routes each node should advertise. If the golden // topology has explicit routable_ips, use those. Otherwise infer - // from the [netmap.NetworkMap] peer AllowedIPs and packet filter dst prefixes. + // from the netmap peer AllowedIPs and packet filter dst prefixes. nodeRoutes := inferNodeRoutes(t, c) // Build approved routes from topology. The topology's approved_routes @@ -181,7 +181,7 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) { srv.App.Change(routeChange) } - // Wait for peers based on golden [netmap.NetworkMap] expected counts. + // Wait for peers based on golden netmap expected counts. for viewerName, cl := range clients { capture := c.Captures[viewerName] if capture.Netmap == nil { @@ -202,8 +202,8 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) { } } - // Ensure all nodes have received at least one [tailcfg.MapResponse], - // including nodes with 0 expected peers that skipped [TestClient.WaitForPeers]. + // Ensure all nodes have received at least one MapResponse, + // including nodes with 0 expected peers that skipped WaitForPeers. for name, cl := range clients { cl.WaitForCondition(t, name+" initial netmap", 15*time.Second, func(nm *netmap.NetworkMap) bool { @@ -211,7 +211,7 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) { }) } - // Compare each viewer's [tailcfg.MapResponse] against the golden [netmap.NetworkMap]. + // Compare each viewer's MapResponse against the golden netmap. for viewerName, cl := range clients { capture := c.Captures[viewerName] if capture.Netmap == nil { @@ -227,8 +227,8 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) { } } -// compareNetmap compares the headscale [tailcfg.MapResponse] against the -// captured [netmap.NetworkMap] data in an IP-independent way. It validates: +// compareNetmap compares the headscale MapResponse against the +// captured netmap data in an IP-independent way. It validates: // - Peer visibility (which peers are present, by hostname) // - Route prefixes in AllowedIPs (non-Tailscale-IP entries like 10.44.0.0/16) // - Number of Tailscale IPs per peer (should be 2: one v4 + one v6) @@ -430,7 +430,7 @@ func compareNetmap( } // saasAddrsByPeer builds a map from SaaS Tailscale address to peer -// hostname using each capture's [tailcfg.NodeView.Addresses]. Peers not in +// hostname using each capture's SelfNode.Addresses. Peers not in // clients are skipped. func saasAddrsByPeer( want testcapture.Node, @@ -442,7 +442,7 @@ func saasAddrsByPeer( return out } - // Walk peers listed in this [netmap.NetworkMap]. + // Walk peers listed in this netmap. for _, peer := range want.Netmap.Peers { name := extractHostname(peer.Name()) if _, isOurs := clients[name]; !isOurs { @@ -457,7 +457,7 @@ func saasAddrsByPeer( } } - // The viewer's own [tailcfg.NodeView] addresses also appear as possible src. + // The viewer's own SelfNode addresses also appear as possible src. if want.Netmap.SelfNode.Valid() { name := extractHostname(want.Netmap.SelfNode.Name()) @@ -533,8 +533,8 @@ func canonicaliseSrcStrings( } // canonicaliseSrcPrefixes is the headscale-side counterpart of -// [canonicaliseSrcStrings], reading already-parsed [netip.Prefix] values -// from [tailcfg.Match.Srcs]. +// canonicaliseSrcStrings, reading already-parsed netip.Prefix values +// from tailcfg.Match.Srcs. func canonicaliseSrcPrefixes( t *testing.T, srcs []netip.Prefix, @@ -627,7 +627,7 @@ type peerSummary struct { PrimaryRoutes []string // sorted } -// parsePrefixOrAddr parses a string as a [netip.Prefix]. If the string +// parsePrefixOrAddr parses a string as a netip.Prefix. If the string // is a bare IP address (no slash), it is converted to a single-host // prefix (/32 for IPv4, /128 for IPv6). Golden data DstPorts.IP can // contain either form. @@ -704,7 +704,7 @@ func countTailscaleIPsView(allowedIPs interface { // inferNodeRoutes determines which routes each node should advertise. // If the topology has explicit routable_ips, those are used. Otherwise -// routes are inferred from the [netmap.NetworkMap] peer AllowedIPs and packet +// routes are inferred from the netmap peer AllowedIPs and packet // filter destination prefixes. func inferNodeRoutes(t *testing.T, c *testcapture.Capture) map[string][]netip.Prefix { t.Helper() @@ -725,7 +725,7 @@ func inferNodeRoutes(t *testing.T, c *testcapture.Capture) map[string][]netip.Pr } } - // Tier 2: infer from each capture's [netmap.NetworkMap] — scan peers with + // Tier 2: infer from each capture's netmap — scan peers with // route prefixes in AllowedIPs. If node X appears as a peer with // route prefix 10.44.0.0/16, then X should advertise that route. for _, node := range c.Captures { diff --git a/hscontrol/servertest/via_ha_compat_test.go b/hscontrol/servertest/via_ha_compat_test.go index 0d166500b..9c652a1af 100644 --- a/hscontrol/servertest/via_ha_compat_test.go +++ b/hscontrol/servertest/via_ha_compat_test.go @@ -46,7 +46,7 @@ var viaHACompatTests = []struct { // TestViaGrantHACompat loads golden captures from Tailscale SaaS that // test via grant steering combined with HA primary route election. // Each capture uses an inline topology with 4-6 nodes (instead of the -// shared 15-node grant topology used by [TestViaGrantMapCompat]). +// shared 15-node grant topology used by TestViaGrantMapCompat). func TestViaGrantHACompat(t *testing.T) { t.Parallel() @@ -175,7 +175,7 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) { } } - // Ensure all nodes have an initial [netmap.NetworkMap]. + // Ensure all nodes have an initial netmap. for name, cl := range clients { cl.WaitForCondition(t, name+" initial netmap", 15*time.Second, func(nm *netmap.NetworkMap) bool { @@ -183,7 +183,7 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) { }) } - // Compare each viewer's [tailcfg.MapResponse] against golden [netmap.NetworkMap]. + // Compare each viewer's MapResponse against golden netmap. for viewerName, cl := range clients { capture := c.Captures[viewerName] if capture.Netmap == nil { @@ -196,9 +196,9 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) { } } -// compareCaptureNetmap compares headscale's [tailcfg.MapResponse] against a -// [testcapture.Node]'s [netmap.NetworkMap] data. Same logic as [compareNetmap] but -// reads from typed [testcapture] fields instead of goldenFile strings. +// compareCaptureNetmap compares headscale's MapResponse against a +// testcapture.Node's netmap data. Same logic as compareNetmap but +// reads from typed testcapture fields instead of goldenFile strings. func compareCaptureNetmap( t *testing.T, viewer *servertest.TestClient, @@ -250,7 +250,7 @@ func compareCaptureNetmap( } } - // Build peer summaries from headscale [tailcfg.MapResponse]. + // Build peer summaries from headscale MapResponse. gotPeers := map[string]capturePeerSummary{} for _, peer := range nm.Peers { @@ -335,7 +335,7 @@ type capturePeerSummary struct { PrimaryRoutes []string } -// captureNodeOrder returns node names from a [testcapture.Capture] +// captureNodeOrder returns node names from a testcapture.Capture // sorted by SaaS node creation time, for deterministic DB ID assignment. // SaaS elects HA primaries by registration order (first registered wins), // which correlates with Created timestamp, not with the random snowflake @@ -377,7 +377,7 @@ func captureNodeOrder(t *testing.T, c *testcapture.Capture) []string { return names } -// convertCapturePolicy converts a [testcapture.Capture]'s policy for headscale, +// convertCapturePolicy converts a testcapture's policy for headscale, // replacing SaaS emails with headscale user format. Fails the test if // none of the known SaaS emails are present: that would mean the // capture was regenerated with a new tag-owner identity and this diff --git a/hscontrol/state/auth_nodekey_binding_test.go b/hscontrol/state/auth_nodekey_binding_test.go deleted file mode 100644 index 8f826df7c..000000000 --- a/hscontrol/state/auth_nodekey_binding_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package state - -import ( - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// TestPreAuthKeyReauthRejectsVictimNodeKey ensures the PAK re-registration -// rotation path enforces the same 1:1 NodeKey<->MachineKey binding the other -// registration paths do. NodeKeys are public (peers learn them from the -// netmap), so an authenticated attacker must not be able to rotate their own -// node's NodeKey onto a victim's NodeKey: doing so poisons the NodeStore -// NodeKey index and denies the victim service. -func TestPreAuthKeyReauthRejectsVictimNodeKey(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - // Attacker owns N_a under U_a with machine key M_a. - attacker := database.CreateUserForTest("attacker") - attackerNode := database.CreateRegisteredNodeForTest(attacker, "attacker-node") - attackerMachineKey := attackerNode.MachineKey - - // Victim owns N_v under U_v with a distinct, public NodeKey K_v. - victim := database.CreateUserForTest("victim") - victimNode := database.CreateRegisteredNodeForTest(victim, "victim-node") - victimNodeKey := victimNode.NodeKey - - require.NotEqual(t, attackerNode.NodeKey, victimNodeKey, - "precondition: attacker and victim must have distinct NodeKeys") - require.NotEqual(t, attackerNode.MachineKey, victimNode.MachineKey, - "precondition: attacker and victim must have distinct MachineKeys") - - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Attacker mints their own valid pre-auth key for their own user. - attackerUserID := types.UserID(attacker.ID) - pak, err := s.CreatePreAuthKey(&attackerUserID, true, false, nil, nil) - require.NoError(t, err) - - // Attacker re-registers over their own noise session (machine key M_a) - // with a valid PAK but carries the victim's NodeKey K_v and a non-past - // expiry (skipping the logout-first branch). - clientExpiry := time.Now().Add(180 * 24 * time.Hour) - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: victimNodeKey, - Expiry: clientExpiry, - Hostinfo: &tailcfg.Hostinfo{ - Hostname: "attacker-node", - }, - } - - _, _, err = s.HandleNodeFromPreAuthKey(regReq, attackerMachineKey) - require.ErrorIs(t, err, ErrNodeKeyInUse, - "attacker must not be able to claim the victim's NodeKey via PAK re-registration") - - // The victim's NodeKey index must still resolve to the victim's node. - resolved, ok := s.nodeStore.GetNodeByNodeKey(victimNodeKey) - require.True(t, ok, "victim NodeKey must still be present in the index") - require.Equal(t, victimNode.MachineKey, resolved.MachineKey(), - "victim NodeKey must remain bound to the victim's MachineKey") -} diff --git a/hscontrol/state/auth_tagged_expiry_test.go b/hscontrol/state/auth_tagged_expiry_test.go deleted file mode 100644 index 4eef8fee7..000000000 --- a/hscontrol/state/auth_tagged_expiry_test.go +++ /dev/null @@ -1,480 +0,0 @@ -package state - -import ( - "fmt" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" - "tailscale.com/types/key" -) - -// TestTaggedReauthKeepsNilExpiry ensures that when an existing tagged node -// re-authenticates through the auth path and re-advertises a tag it is still -// permitted to hold, it stays tagged AND keeps key-expiry disabled (nil). -// Tagged nodes never expire, so applyAuthNodeUpdate must not assign an expiry -// to a node that remains tagged just because the auth used the -// convert-from-tag lookup path. -func TestTaggedReauthKeepsNilExpiry(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("reauth-user") - node := database.CreateRegisteredNodeForTest(user, "reauth-node") - machineKey := node.MachineKey - nodeID := node.ID - nodeKey := node.NodeKey - discoKey := node.DiscoKey - - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Make the node tagged with tag:foo and Expiry=nil. Leaving UserID nil - // indexes it under userID 0 so the same-user machine-key lookup misses and - // HandleNodeFromAuthPath takes the convert-from-tag branch. The User field - // is retained for created-by tracking, which lets the tag re-advertisement - // be permitted. - seeded, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Tags = []string{"tag:foo"} - n.UserID = nil - n.User = user - n.Expiry = nil - }) - require.True(t, ok) - require.True(t, seeded.IsTagged(), "precondition: node must be tagged") - require.True(t, seeded.Valid()) - - policy := fmt.Sprintf(`{"tagOwners":{"tag:foo":["%s@"]}}`, user.Name) - _, err = s.SetPolicy([]byte(policy)) - require.NoError(t, err) - require.True(t, s.NodeCanHaveTag(seeded, "tag:foo"), - "precondition: tagged node must be permitted to re-advertise tag:foo") - - // Registration that re-advertises tag:foo and carries a non-nil client - // expiry (the normal tailscale client case). - clientExpiry := time.Now().Add(180 * 24 * time.Hour) - regData := &types.RegistrationData{ - MachineKey: machineKey, - NodeKey: nodeKey, - DiscoKey: discoKey, - Hostname: "reauth-node", - Hostinfo: &tailcfg.Hostinfo{ - Hostname: "reauth-node", - RequestTags: []string{"tag:foo"}, - }, - Expiry: &clientExpiry, - } - - authID := types.MustAuthID() - s.SetAuthCacheEntry(authID, types.NewRegisterAuthRequest(regData)) - - finalNode, _, err := s.HandleNodeFromAuthPath( - authID, - types.UserID(user.ID), - nil, - util.RegisterMethodOIDC, - ) - require.NoError(t, err) - require.True(t, finalNode.Valid()) - - require.True(t, finalNode.IsTagged(), - "node should remain tagged after re-advertising a permitted tag") - - require.Nil(t, finalNode.AsStruct().Expiry, - "tagged node must keep nil key expiry (tagged nodes never expire)") -} - -// TestTaggedReauthWithReusedUserPAK reproduces issue #3312: a containerized -// node registered with a user-owned one-shot pre-auth key, then converted to a -// tagged node (UserID cleared to NULL), is logged out when the container -// restarts and re-registers with the SAME, now-used TS_AUTHKEY. -// -// Root cause: findExistingNodeForPAK (state.go) looks the node up by the PAK's -// owning user (alice). After tagging, the node is indexed under UserID(0), so -// the same-user machine-key lookup misses, the re-registration fast-path is -// skipped, and the already-used one-shot PAK is re-validated and rejected with -// "authkey already used" — logging the node out. -// -// https://github.com/juanfont/headscale/issues/3312 -func TestTaggedReauthWithReusedUserPAK(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("authkey-user") - - policy := fmt.Sprintf(`{"tagOwners":{"tag:foo":["%s@"]}}`, user.Name) - _, err = s.SetPolicy([]byte(policy)) - require.NoError(t, err) - - // One-shot, user-owned PAK: `headscale preauthkeys create -u 1`. - pak, err := s.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - nodeKey := key.NewNode() - - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "authkey-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - - // First registration: node joins as alice, the one-shot PAK is consumed. - first, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.True(t, first.Valid()) - nodeID := first.ID() - - // `headscale nodes tag -t tag:foo`: convert to a tagged node. This clears - // both UserID and User (state.SetNodeTags), diverging the node's ownership - // from the still-user-owned PAK. - tagged, _, err := s.SetNodeTags(nodeID, []string{"tag:foo"}) - require.NoError(t, err) - require.True(t, tagged.IsTagged(), "precondition: node must be tagged") - - // Container restart: the same node re-registers with the SAME, now-used - // one-shot TS_AUTHKEY. The machine key proves identity, so this must - // succeed. It currently fails with "authkey already used". - second, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err, - "re-registration with a reused user PAK on a tagged node must not be rejected") - require.True(t, second.Valid()) - require.True(t, second.IsTagged(), "node must remain tagged after re-registration") - require.Equal(t, nodeID, second.ID(), - "must update the existing node, not create a new one") -} - -// reregisterExpiredUserNodeWithSpentKey registers a user-owned node with a -// one-shot key, forces it into the expired state, and re-registers with the -// same spent key. sameNodeKey distinguishes the two re-auth shapes: -// - false: the node rotates its node key (normal tailscale client on re-auth) -// - true: the node reuses its node key -// -// In both cases an expired node is genuinely re-authenticating and must present -// a valid key; a spent one-shot key must be rejected. -func reregisterExpiredUserNodeWithSpentKey(t *testing.T, sameNodeKey bool) (types.NodeView, error) { - t.Helper() - - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("expired-user") - - pak, err := s.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - nodeKey := key.NewNode() - - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "expired-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - - first, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.True(t, first.Valid()) - require.False(t, first.IsTagged(), "precondition: node must be user-owned") - - // Force the node into the expired state. - past := time.Now().Add(-1 * time.Hour) - _, ok := s.nodeStore.UpdateNode(first.ID(), func(n *types.Node) { - n.Expiry = &past - }) - require.True(t, ok) - - reReg := regReq - if !sameNodeKey { - reReg.NodeKey = key.NewNode().Public() - } - - node, _, err := s.HandleNodeFromPreAuthKey(reReg, machineKey.Public()) - - return node, err -} - -// TestExpiredUserNodeReusedOneShotKey_RotatedNodeKey: a node rotating its node -// key on re-auth is already a key rotation, so the key is re-validated. -func TestExpiredUserNodeReusedOneShotKey_RotatedNodeKey(t *testing.T) { - _, err := reregisterExpiredUserNodeWithSpentKey(t, false) - require.Error(t, err, - "expired node re-authenticating with a rotated node key must present a valid key") - require.Contains(t, err.Error(), "authkey already used") -} - -// TestExpiredUserNodeReusedOneShotKey_SameNodeKey: the security boundary must -// not depend on the client rotating its node key. An expired node re-using its -// node key must still re-validate the key, otherwise a spent one-shot key -// silently re-authorises it. -func TestExpiredUserNodeReusedOneShotKey_SameNodeKey(t *testing.T) { - _, err := reregisterExpiredUserNodeWithSpentKey(t, true) - require.Error(t, err, - "expired node re-registering with the same node key must re-validate its key") - require.Contains(t, err.Error(), "authkey already used") -} - -// TestReusableUserPAKReauthOnTaggedNodeNoDuplicate guards against a reusable -// user pre-auth key creating a second node when it is re-presented for a node -// that has since been converted to tagged. The node must be updated in place. -func TestReusableUserPAKReauthOnTaggedNodeNoDuplicate(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("reusable-user") - - policy := fmt.Sprintf(`{"tagOwners":{"tag:foo":["%s@"]}}`, user.Name) - _, err = s.SetPolicy([]byte(policy)) - require.NoError(t, err) - - pak, err := s.CreatePreAuthKey(user.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "reusable-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - - first, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - - _, _, err = s.SetNodeTags(first.ID(), []string{"tag:foo"}) - require.NoError(t, err) - - second, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.True(t, second.IsTagged()) - require.Equal(t, first.ID(), second.ID(), "must update in place, not duplicate") - require.Equal(t, 1, s.ListNodes().Len(), "machine must map to exactly one node") -} - -// TestTaggedPAKReauthConvertsUserOwnedNode ensures presenting a tagged pre-auth -// key for a machine that already has a user-owned node converts that node in -// place (same machine, new ownership) rather than creating a duplicate. -func TestTaggedPAKReauthConvertsUserOwnedNode(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("owner") - - userPak, err := s.CreatePreAuthKey(user.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - nodeKey := key.NewNode() - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: userPak.Key}, - NodeKey: nodeKey.Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "owned-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - - owned, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - require.False(t, owned.IsTagged(), "precondition: node is user-owned") - - // A tags-only key re-registers the same machine (same node key). - taggedPak, err := s.CreatePreAuthKey(nil, true, false, nil, []string{"tag:foo"}) - require.NoError(t, err) - - convReq := regReq - convReq.Auth = &tailcfg.RegisterResponseAuth{AuthKey: taggedPak.Key} - - converted, _, err := s.HandleNodeFromPreAuthKey(convReq, machineKey.Public()) - require.NoError(t, err) - require.Equal(t, owned.ID(), converted.ID(), "must convert in place, not duplicate") - require.True(t, converted.IsTagged(), "node must become tagged") - require.Equal(t, []string{"tag:foo"}, converted.Tags().AsSlice()) - require.Equal(t, 1, s.ListNodes().Len(), "machine must map to exactly one node") -} - -// registerTwoUsersOnOneMachine registers two user-owned nodes that share a -// machine key (the "create new, do not transfer" multi-user device state) and -// returns the State and the shared machine key. -func registerTwoUsersOnOneMachine(t *testing.T) (*State, key.MachinePublic, types.NodeID) { - t.Helper() - - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - u1 := s.CreateUserForTest("u1") - u2 := s.CreateUserForTest("u2") - mk := key.NewMachine() - - reg := func(pakUser *types.User) types.NodeView { - pak, err := s.CreatePreAuthKey(pakUser.TypedID(), true, false, nil, nil) - require.NoError(t, err) - n, _, err := s.HandleNodeFromPreAuthKey(tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "multi"}, - Expiry: time.Now().Add(24 * time.Hour), - }, mk.Public()) - require.NoError(t, err) - - return n - } - - n1 := reg(u1) - n2 := reg(u2) - require.NotEqual(t, n1.ID(), n2.ID(), "precondition: two distinct nodes share the machine key") - require.Equal(t, 2, s.ListNodes().Len()) - - return s, mk.Public(), n1.ID() -} - -// TestTaggedPAKReauthRejectsAmbiguousMultiUserNode: a tagged pre-auth key on a -// machine that has more than one user-owned node cannot know which to convert, -// so the registration is rejected rather than converting an arbitrary one and -// orphaning the rest. -func TestTaggedPAKReauthRejectsAmbiguousMultiUserNode(t *testing.T) { - s, mk, _ := registerTwoUsersOnOneMachine(t) - - taggedPak, err := s.CreatePreAuthKey(nil, true, false, nil, []string{"tag:foo"}) - require.NoError(t, err) - - _, _, err = s.HandleNodeFromPreAuthKey(tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: taggedPak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "multi"}, - Expiry: time.Now().Add(24 * time.Hour), - }, mk) - require.ErrorIs(t, err, ErrAmbiguousNodeOwnership) - require.Equal(t, 2, s.ListNodes().Len(), "no node created or converted on rejection") -} - -// TestAuthPathRejectsTaggedAndUserCoexistence: if a machine key ends up with -// both a tagged node and a user-owned node (impossible per validateNodeOwnership, -// but reachable by tagging one node of a multi-user device via the admin path), -// an OIDC re-auth must reject rather than silently converting the tagged node -// and orphaning the user-owned one. -func TestAuthPathRejectsTaggedAndUserCoexistence(t *testing.T) { - s, mk, n1 := registerTwoUsersOnOneMachine(t) - - // Tag one of the two user-owned nodes -> {0: tagged, u2: user-owned} coexist. - _, err := s.SetPolicy([]byte(`{"tagOwners":{"tag:foo":["u1@"]}}`)) - require.NoError(t, err) - tagged, _, err := s.SetNodeTags(n1, []string{"tag:foo"}) - require.NoError(t, err) - require.True(t, tagged.IsTagged()) - - // A third user authenticates the same machine via OIDC. - u3 := s.CreateUserForTest("u3") - regData := &types.RegistrationData{ - MachineKey: mk, - NodeKey: key.NewNode().Public(), - Hostname: "multi", - Hostinfo: &tailcfg.Hostinfo{Hostname: "multi"}, - } - authID := types.MustAuthID() - s.SetAuthCacheEntry(authID, types.NewRegisterAuthRequest(regData)) - - _, _, err = s.HandleNodeFromAuthPath(authID, types.UserID(u3.ID), nil, util.RegisterMethodOIDC) - require.ErrorIs(t, err, ErrAmbiguousNodeOwnership) -} - -// TestTaggedNodeCanHaveKeyExpiry matches Tailscale: a tagged node has key -// expiry disabled by default, but it can still be set explicitly (e.g. via -// `headscale nodes expire`). -func TestTaggedNodeCanHaveKeyExpiry(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - _, err = s.SetPolicy([]byte(`{"tagOwners":{"tag:foo":["tagger@"]}}`)) - require.NoError(t, err) - - pak, err := s.CreatePreAuthKey(nil, true, false, nil, []string{"tag:foo"}) - require.NoError(t, err) - - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "tagged-node"}, - } - node, _, err := s.HandleNodeFromPreAuthKey(regReq, key.NewMachine().Public()) - require.NoError(t, err) - require.True(t, node.IsTagged()) - require.Nil(t, node.AsStruct().Expiry, "key expiry is disabled by default for tagged nodes") - - expiry := time.Now().Add(24 * time.Hour) - after, _, err := s.SetNodeExpiry(node.ID(), &expiry) - require.NoError(t, err) - require.True(t, after.IsTagged(), "node stays tagged") - require.NotNil(t, after.AsStruct().Expiry, "expiry can be set on a tagged node") - require.Equal(t, expiry.Unix(), after.AsStruct().Expiry.Unix()) -} - -// TestTaggingPreservesNodeExpiry matches Tailscale: changing a node's tags does -// not alter its key expiry (expiry only changes on re-authentication). -func TestTaggingPreservesNodeExpiry(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("owner") - - _, err = s.SetPolicy(fmt.Appendf(nil, `{"tagOwners":{"tag:foo":["%s@"]}}`, user.Name)) - require.NoError(t, err) - - pak, err := s.CreatePreAuthKey(user.TypedID(), false, false, nil, nil) - require.NoError(t, err) - - expiry := time.Now().Add(24 * time.Hour) - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "owned-node"}, - Expiry: expiry, - } - node, _, err := s.HandleNodeFromPreAuthKey(regReq, key.NewMachine().Public()) - require.NoError(t, err) - require.NotNil(t, node.AsStruct().Expiry, "precondition: user node has an expiry") - - tagged, _, err := s.SetNodeTags(node.ID(), []string{"tag:foo"}) - require.NoError(t, err) - require.True(t, tagged.IsTagged()) - require.NotNil(t, tagged.AsStruct().Expiry, "tag change must not clear expiry") - require.Equal(t, expiry.Unix(), tagged.AsStruct().Expiry.Unix()) -} diff --git a/hscontrol/state/autoapprove_test.go b/hscontrol/state/autoapprove_test.go deleted file mode 100644 index 444a04764..000000000 --- a/hscontrol/state/autoapprove_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package state - -import ( - "net/netip" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// TestAutoApproveBatchApprovesRoutes verifies the batched autoApproveNodes still -// approves a node's advertised route when policy auto-approvers permit it. The -// batching collapses the per-node SetApprovedRoutes calls into one NodeStore -// update and one policy rebuild; this guards that correctness is preserved. -func TestAutoApproveBatchApprovesRoutes(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - route := netip.MustParsePrefix("10.0.0.0/24") - - _, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{route}} - }) - require.True(t, ok) - - pol := `{ - "autoApprovers": {"routes": {"10.0.0.0/24": ["persist-user@"]}}, - "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] - }` - _, err := s.SetPolicy([]byte(pol)) - require.NoError(t, err) - - _, err = s.ReloadPolicy() - require.NoError(t, err) - - nv, ok := s.GetNodeByID(nodeID) - require.True(t, ok) - assert.Contains(t, nv.ApprovedRoutes().AsSlice(), route, - "auto-approver should have approved the advertised route") -} diff --git a/hscontrol/state/connect_test.go b/hscontrol/state/connect_test.go deleted file mode 100644 index be73b5f26..000000000 --- a/hscontrol/state/connect_test.go +++ /dev/null @@ -1,279 +0,0 @@ -package state - -import ( - "net/netip" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/types/change" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// runtimePeerComputationReasons returns the Reason of every change in cs that -// carries RequiresRuntimePeerComputation. Each such change makes the batcher -// rebuild a full netmap (packet filters, SSH policy, peer serialization) for -// every connected node, so it is the expensive fan-out the issue is about. -func runtimePeerComputationReasons(cs []change.Change) []string { - var reasons []string - - for _, c := range cs { - if c.RequiresRuntimePeerComputation { - reasons = append(reasons, c.Reason) - } - } - - return reasons -} - -// hasPeerPatch reports whether any change carries a lightweight PeerChange -// patch (e.g. the online/offline indicator). This is the cheap notification a -// reconnect should produce instead of a full runtime recompute. -func hasPeerPatch(cs []change.Change) bool { - for _, c := range cs { - if len(c.PeerPatches) > 0 { - return true - } - } - - return false -} - -// forcesPeerRecompute reports whether any change makes peers rebuild a full -// netmap, whether via a full update (subnet-router path) or a runtime peer -// computation (relay/via path). -func forcesPeerRecompute(cs []change.Change) bool { - for _, c := range cs { - if c.IsFull() || c.RequiresRuntimePeerComputation { - return true - } - } - - return false -} - -// TestConnectDisconnectOrdinaryNodeNoRuntimeRecompute asserts that an ordinary -// node coming online or going offline only sends the lightweight online/offline -// peer patch and does not trigger a runtime peer recompute. -// -// State.Connect and State.Disconnect gate change.PolicyChange() (which sets -// RequiresRuntimePeerComputation, forcing the batcher to rebuild a full netmap -// for every connected node) on NodeNeedsPeerRecompute. An ordinary node is -// neither a subnet router, a relay target, nor a via target, so the gate is -// false and no recompute is emitted. -// -// Emitting that recompute unconditionally turned each reconnect into O(N) full -// netmap rebuilds (and a reconnect storm into O(N^2)), which saturated CPU -// after the v0.28 -> v0.29 upgrade. -func TestConnectDisconnectOrdinaryNodeNoRuntimeRecompute(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - t.Run("connect", func(t *testing.T) { - cs, epoch := s.Connect(nodeID) - require.NotZero(t, epoch, "Connect should return a session epoch") - - assert.True(t, hasPeerPatch(cs), - "Connect should still emit a lightweight online peer patch") - - reasons := runtimePeerComputationReasons(cs) - assert.Empty(t, reasons, - "ordinary node connect must not trigger a runtime peer recompute; "+ - "got RequiresRuntimePeerComputation changes: %v", reasons) - }) - - t.Run("disconnect", func(t *testing.T) { - // Connect acquired a session in the connect subtest too; drain to the - // last release, which is the one that marks the node offline. - _, epoch := s.Connect(nodeID) - cs := drainSessions(t, s, nodeID, epoch) - - assert.True(t, hasPeerPatch(cs), - "Disconnect should still emit a lightweight offline peer patch") - - reasons := runtimePeerComputationReasons(cs) - assert.Empty(t, reasons, - "ordinary node disconnect must not trigger a runtime peer recompute; "+ - "got RequiresRuntimePeerComputation changes: %v", reasons) - }) -} - -// TestConnectDisconnectRelayTargetTriggersRecompute locks the cap/relay case: -// a relay target is not a subnet router, so the only thing that makes its -// connect/disconnect emit a runtime peer recompute is the -// NodeNeedsPeerRecompute gate. Peers must still receive that recompute so they -// drop a stale PeerRelay allocation when the relay goes offline. -func TestConnectDisconnectRelayTargetTriggersRecompute(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - // A cap/relay grant whose destination resolves to the node's owning - // user makes the node a relay target without making it a subnet router. - relayPolicy := `{"grants":[{"src":["*"],"dst":["persist-user@"],"app":{"tailscale.com/cap/relay":[{}]}}]}` - _, err := s.SetPolicy([]byte(relayPolicy)) - require.NoError(t, err) - - t.Run("connect", func(t *testing.T) { - cs, epoch := s.Connect(nodeID) - require.NotZero(t, epoch) - - assert.NotEmpty(t, runtimePeerComputationReasons(cs), - "relay-target node connect must trigger a runtime peer recompute") - }) - - t.Run("disconnect", func(t *testing.T) { - _, epoch := s.Connect(nodeID) - cs := drainSessions(t, s, nodeID, epoch) - - assert.NotEmpty(t, runtimePeerComputationReasons(cs), - "relay-target node disconnect must trigger a runtime peer recompute") - }) -} - -// TestConnectDisconnectSubnetRouterForcesRecompute guards that a subnet router -// still forces peers to recompute on connect/disconnect (primary-route -// failover changes their AllowedIPs), so the gate does not over-suppress. -func TestConnectDisconnectSubnetRouterForcesRecompute(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - route := netip.MustParsePrefix("10.0.0.0/24") - _, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{route}} - n.ApprovedRoutes = []netip.Prefix{route} - }) - require.True(t, ok) - - t.Run("connect", func(t *testing.T) { - cs, epoch := s.Connect(nodeID) - require.NotZero(t, epoch) - - assert.True(t, forcesPeerRecompute(cs), - "subnet router connect must force a peer recompute") - }) - - t.Run("disconnect", func(t *testing.T) { - _, epoch := s.Connect(nodeID) - cs := drainSessions(t, s, nodeID, epoch) - - assert.True(t, forcesPeerRecompute(cs), - "subnet router disconnect must force a peer recompute") - }) -} - -// TestConnectDisconnectSubnetRouterEmitsPolicyChangeNotFull pins how a subnet -// router forces that recompute: through the gated change.PolicyChange() (a -// runtime peer recompute) and the lightweight online/offline peer patch, not a -// full update. policyChangeResponse is a strict subset of a full update yet -// still carries primary-route failover, so the heavier FullUpdate that the -// online/offline change once emitted for subnet routers is unnecessary. -func TestConnectDisconnectSubnetRouterEmitsPolicyChangeNotFull(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - route := netip.MustParsePrefix("10.0.0.0/24") - _, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Hostinfo = &tailcfg.Hostinfo{RoutableIPs: []netip.Prefix{route}} - n.ApprovedRoutes = []netip.Prefix{route} - }) - require.True(t, ok) - - assertRecomputeNotFull := func(t *testing.T, cs []change.Change) { - t.Helper() - - assert.NotEmpty(t, runtimePeerComputationReasons(cs), - "subnet router must still drive a runtime peer recompute") - assert.True(t, hasPeerPatch(cs), - "subnet router should still emit the lightweight online/offline patch") - - for _, c := range cs { - assert.Falsef(t, c.IsFull(), - "subnet router recompute must be a PolicyChange, not a full update: %q", c.Reason) - } - } - - t.Run("connect", func(t *testing.T) { - cs, epoch := s.Connect(nodeID) - require.NotZero(t, epoch) - - assertRecomputeNotFull(t, cs) - }) - - t.Run("disconnect", func(t *testing.T) { - _, epoch := s.Connect(nodeID) - cs := drainSessions(t, s, nodeID, epoch) - - assertRecomputeNotFull(t, cs) - }) -} - -// drainSessions releases poll sessions on nodeID until the node goes -// offline, returning the changes from the final release (the one that -// emits the offline notifications). Fails the test if the node is -// still online after releasing as many sessions as Connect calls could -// plausibly have acquired. -func drainSessions(t *testing.T, s *State, nodeID types.NodeID, epoch uint64) []change.Change { - t.Helper() - - // Arbitrary upper bound: comfortably above the number of Connect - // calls any test here makes. It only guards against looping - // forever when the node never goes offline. - const maxSessions = 16 - - for range maxSessions { - cs, err := s.Disconnect(nodeID, epoch) - require.NoError(t, err) - - if len(cs) > 0 { - return cs - } - } - - t.Fatalf("node %d still online after releasing %d sessions", nodeID, maxSessions) - - return nil -} - -// TestDisconnectOutOfOrderSessionsCannotStrandNodeOnline reproduces the -// server side of the relogin flake at the state level: a cancelled map -// request whose handler runs late acquires a session (and the newest -// epoch) after the real session's Connect, then releases without taking -// the node offline because the real session is still live. The real -// session's release — carrying the older epoch — must still take the -// node offline. Under the old epoch-equality gate it was rejected as -// stale and the node stayed online forever. -func TestDisconnectOutOfOrderSessionsCannotStrandNodeOnline(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - _, liveGen := s.Connect(nodeID) - _, zombieGen := s.Connect(nodeID) - require.Greater(t, zombieGen, liveGen, "late session must hold the newer epoch") - - // The zombie session dies first; another session is live, so the node - // must stay online and no offline changes may be emitted. - cs, err := s.Disconnect(nodeID, zombieGen) - require.NoError(t, err) - assert.Empty(t, cs, "release with another live session must not emit changes") - - nv, ok := s.GetNodeByID(nodeID) - require.True(t, ok) - - online, known := nv.IsOnline().GetOk() - require.True(t, known) - assert.True(t, online, "node must stay online while the real session lives") - - // The real session releases last, with the older epoch. This must take - // the node offline. - cs, err = s.Disconnect(nodeID, liveGen) - require.NoError(t, err) - assert.True(t, hasPeerPatch(cs), "final release must emit the offline peer patch") - - nv, ok = s.GetNodeByID(nodeID) - require.True(t, ok) - - online, known = nv.IsOnline().GetOk() - require.True(t, known) - assert.False(t, online, "node must be offline after its last session is released") -} diff --git a/hscontrol/state/debug.go b/hscontrol/state/debug.go index 91a9c4493..623bb8606 100644 --- a/hscontrol/state/debug.go +++ b/hscontrol/state/debug.go @@ -62,23 +62,51 @@ type DebugStringInfo struct { // DebugOverview returns a comprehensive overview of the current state for debugging. func (s *State) DebugOverview() string { - info := s.DebugOverviewJSON() + allNodes := s.nodeStore.ListNodes() + users, _ := s.ListAllUsers() var sb strings.Builder sb.WriteString("=== Headscale State Overview ===\n\n") // Node statistics - fmt.Fprintf(&sb, "Nodes: %d total\n", info.Nodes.Total) - fmt.Fprintf(&sb, " - Online: %d\n", info.Nodes.Online) - fmt.Fprintf(&sb, " - Expired: %d\n", info.Nodes.Expired) - fmt.Fprintf(&sb, " - Ephemeral: %d\n", info.Nodes.Ephemeral) + fmt.Fprintf(&sb, "Nodes: %d total\n", allNodes.Len()) + + userNodeCounts := make(map[string]int) + onlineCount := 0 + expiredCount := 0 + ephemeralCount := 0 + + now := time.Now() + + for _, node := range allNodes.All() { + if node.Valid() { + userName := node.Owner().Name() + userNodeCounts[userName]++ + + if node.IsOnline().Valid() && node.IsOnline().Get() { + onlineCount++ + } + + if node.Expiry().Valid() && node.Expiry().Get().Before(now) { + expiredCount++ + } + + if node.AuthKey().Valid() && node.AuthKey().Ephemeral() { + ephemeralCount++ + } + } + } + + fmt.Fprintf(&sb, " - Online: %d\n", onlineCount) + fmt.Fprintf(&sb, " - Expired: %d\n", expiredCount) + fmt.Fprintf(&sb, " - Ephemeral: %d\n", ephemeralCount) sb.WriteString("\n") // User statistics - fmt.Fprintf(&sb, "Users: %d total\n", info.TotalUsers) + fmt.Fprintf(&sb, "Users: %d total\n", len(users)) - for userName, nodeCount := range info.Users { + for userName, nodeCount := range userNodeCounts { fmt.Fprintf(&sb, " - %s: %d nodes\n", userName, nodeCount) } @@ -86,17 +114,18 @@ func (s *State) DebugOverview() string { // Policy information sb.WriteString("Policy:\n") - fmt.Fprintf(&sb, " - Mode: %s\n", info.Policy.Mode) + fmt.Fprintf(&sb, " - Mode: %s\n", s.cfg.Policy.Mode) - if info.Policy.Mode == string(types.PolicyModeFile) { - fmt.Fprintf(&sb, " - Path: %s\n", info.Policy.Path) + if s.cfg.Policy.Mode == types.PolicyModeFile { + fmt.Fprintf(&sb, " - Path: %s\n", s.cfg.Policy.Path) } sb.WriteString("\n") // DERP information - if info.DERP.Configured { - fmt.Fprintf(&sb, "DERP: %d regions configured\n", info.DERP.Regions) + derpMap := s.derpMap.Load() + if derpMap != nil { + fmt.Fprintf(&sb, "DERP: %d regions configured\n", len(derpMap.Regions)) } else { sb.WriteString("DERP: not configured\n") } @@ -104,7 +133,14 @@ func (s *State) DebugOverview() string { sb.WriteString("\n") // Route information - fmt.Fprintf(&sb, "Primary Routes: %d active\n", info.PrimaryRoutes) + primaryStr := s.PrimaryRoutesString() + + routeCount := len(strings.Split(strings.TrimSpace(primaryStr), "\n")) + if primaryStr == "" { + routeCount = 0 + } + + fmt.Fprintf(&sb, "Primary Routes: %d active\n", routeCount) sb.WriteString("\n") // Registration cache @@ -114,7 +150,7 @@ func (s *State) DebugOverview() string { return sb.String() } -// DebugNodeStore returns debug information about the [NodeStore]. +// DebugNodeStore returns debug information about the NodeStore. func (s *State) DebugNodeStore() string { return s.nodeStore.DebugString() } @@ -221,7 +257,7 @@ func (s *State) DebugFilter() ([]tailcfg.FilterRule, error) { } // DebugRoutes returns the current primary routes information as a -// structured object built from the [NodeStore] snapshot. +// structured object built from the NodeStore snapshot. func (s *State) DebugRoutes() types.DebugRoutes { debug := types.DebugRoutes{ AvailableRoutes: make(map[types.NodeID][]netip.Prefix), @@ -335,7 +371,14 @@ func (s *State) DebugOverviewJSON() DebugOverviewInfo { } // Route information - info.PrimaryRoutes = len(s.nodeStore.PrimaryRoutes()) + primaryStr := s.PrimaryRoutesString() + + routeCount := len(strings.Split(strings.TrimSpace(primaryStr), "\n")) + if primaryStr == "" { + routeCount = 0 + } + + info.PrimaryRoutes = routeCount return info } @@ -378,7 +421,7 @@ func (s *State) DebugDERPJSON() DebugDERPInfo { return info } -// DebugNodeStoreJSON returns the actual nodes map from the current [NodeStore] snapshot. +// DebugNodeStoreJSON returns the actual nodes map from the current NodeStore snapshot. func (s *State) DebugNodeStoreJSON() map[types.NodeID]types.Node { snapshot := s.nodeStore.data.Load() return snapshot.nodesByID diff --git a/hscontrol/state/endpoint_test.go b/hscontrol/state/endpoint_test.go index 21b83e5b6..b8905ab7b 100644 --- a/hscontrol/state/endpoint_test.go +++ b/hscontrol/state/endpoint_test.go @@ -111,88 +111,3 @@ func TestEndpointStorageInNodeStore(t *testing.T) { } } } - -// TestEndpointBroadcastWorthy verifies the gate that decides whether an -// endpoint-only delta is worth fanning out to peers as an incremental -// PeersChangedPatch. A delta that only adds STUN-derived endpoints (or only -// removes endpoints) is suppressed: it is churny and unlikely to be useful, -// and disco's callMeMaybe re-derives STUN paths anyway. Only deltas that -// introduce a genuinely useful (non-STUN) endpoint are broadcast-worthy. -func TestEndpointBroadcastWorthy(t *testing.T) { - local := netip.MustParseAddrPort("192.168.1.5:41641") - local2 := netip.MustParseAddrPort("192.168.1.6:41641") - stun := netip.MustParseAddrPort("203.0.113.7:41641") - stun2 := netip.MustParseAddrPort("203.0.113.8:41641") - portmap := netip.MustParseAddrPort("198.51.100.9:41641") - - tests := []struct { - name string - stored []netip.AddrPort - newEPs []netip.AddrPort - newType []tailcfg.EndpointType - want bool - }{ - { - name: "adds only a STUN endpoint - suppress", - stored: []netip.AddrPort{local}, - newEPs: []netip.AddrPort{local, stun}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal, tailcfg.EndpointSTUN}, - want: false, - }, - { - name: "adds only STUN4LocalPort - suppress", - stored: []netip.AddrPort{local}, - newEPs: []netip.AddrPort{local, stun}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal, tailcfg.EndpointSTUN4LocalPort}, - want: false, - }, - { - name: "adds a useful local endpoint - broadcast", - stored: []netip.AddrPort{stun}, - newEPs: []netip.AddrPort{stun, local}, - newType: []tailcfg.EndpointType{tailcfg.EndpointSTUN, tailcfg.EndpointLocal}, - want: true, - }, - { - name: "adds a useful portmapped endpoint - broadcast", - stored: []netip.AddrPort{local}, - newEPs: []netip.AddrPort{local, portmap}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal, tailcfg.EndpointPortmapped}, - want: true, - }, - { - name: "pure shrink, no additions - suppress", - stored: []netip.AddrPort{local, local2}, - newEPs: []netip.AddrPort{local}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal}, - want: false, - }, - { - name: "nil types (older client) adding endpoint - broadcast", - stored: []netip.AddrPort{local}, - newEPs: []netip.AddrPort{local, local2}, - want: true, - }, - { - name: "only STUN endpoints churn (replace one STUN with another) - suppress", - stored: []netip.AddrPort{local, stun}, - newEPs: []netip.AddrPort{local, stun2}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal, tailcfg.EndpointSTUN}, - want: false, - }, - { - name: "mixed add: one STUN and one useful - broadcast", - stored: []netip.AddrPort{local}, - newEPs: []netip.AddrPort{local, stun, local2}, - newType: []tailcfg.EndpointType{tailcfg.EndpointLocal, tailcfg.EndpointSTUN, tailcfg.EndpointLocal}, - want: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := endpointBroadcastWorthy(tt.stored, tt.newEPs, tt.newType) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/hscontrol/state/ha_health.go b/hscontrol/state/ha_health.go index 4e76e9a35..2b3e247fe 100644 --- a/hscontrol/state/ha_health.go +++ b/hscontrol/state/ha_health.go @@ -3,13 +3,11 @@ package state import ( "context" "sync" - "sync/atomic" "time" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types/change" "github.com/juanfont/headscale/hscontrol/util/zlog/zf" - "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog/log" "tailscale.com/tailcfg" "tailscale.com/util/set" @@ -22,14 +20,9 @@ type HAHealthProber struct { cfg types.HARouteConfig serverURL string isConnected func(types.NodeID) bool - - // lastStableSession defers a timeout-driven unhealthy decision - // for sessions younger than one probe cycle, giving wgengine - // time to apply the new netmap on a freshly reconnected node. - lastStableSession *xsync.Map[types.NodeID, uint64] } -// NewHAHealthProber creates a prober that uses the given [State] for +// NewHAHealthProber creates a prober that uses the given State for // ping tracking and primary route management. // isConnected should return true if a node has an active map session. func NewHAHealthProber( @@ -39,80 +32,44 @@ func NewHAHealthProber( isConnected func(types.NodeID) bool, ) *HAHealthProber { return &HAHealthProber{ - state: s, - cfg: cfg, - serverURL: serverURL, - isConnected: isConnected, - lastStableSession: xsync.NewMap[types.NodeID, uint64](), + state: s, + cfg: cfg, + serverURL: serverURL, + isConnected: isConnected, } } -// markSessionStable records session and returns true iff the same -// value was already present from a prior cycle. -func (p *HAHealthProber) markSessionStable(id types.NodeID, session uint64) bool { - prev, loaded := p.lastStableSession.LoadAndStore(id, session) - return loaded && prev == session -} - -// forgetSession drops the recorded session so a node returning to -// HA candidacy starts fresh. -func (p *HAHealthProber) forgetSession(id types.NodeID) { - p.lastStableSession.Delete(id) -} - -// ProbeOnce pings every HA subnet router and applies the cycle's -// results in one batch so the election sees a single transition. -// Per-result snapshots could otherwise elect a node that the next -// snapshot demotes again, flipping primary onto an unreachable peer. -// A timeout that fires after the node reconnected, or against a -// session younger than one probe cycle, is dropped so wgengine has -// time to apply the new netmap before silence is read as -// unreachability. +// ProbeOnce pings all HA subnet router nodes. PingNode changes are +// dispatched immediately via dispatch so nodes can respond before the +// timeout. Health-related policy changes are also dispatched inline. func (p *HAHealthProber) ProbeOnce( ctx context.Context, dispatch func(...change.Change), ) { haNodes := p.state.nodeStore.HANodes() + if len(haNodes) == 0 { + return + } - // Build the deduplicated node-ID set and slice in one pass. - var nodeIDs []types.NodeID - + // Deduplicate node IDs across prefixes. seen := make(set.Set[types.NodeID]) + var nodeIDs []types.NodeID + for _, nodes := range haNodes { for _, id := range nodes { - if seen.Contains(id) { - continue + if !seen.Contains(id) { + seen.Add(id) + nodeIDs = append(nodeIDs, id) } - - seen.Add(id) - nodeIDs = append(nodeIDs, id) } } - // Drop stable-session entries for nodes that are no longer HA - // candidates so a future reappearance starts fresh. - p.lastStableSession.Range(func(id types.NodeID, _ uint64) bool { - if !seen.Contains(id) { - p.lastStableSession.Delete(id) - } - - return true - }) - - if len(haNodes) == 0 { - return - } - log.Debug(). Int("haNodes", len(nodeIDs)). Msg("HA health prober starting probe cycle") - var ( - wg sync.WaitGroup - results = xsync.NewMap[types.NodeID, bool]() - deferred atomic.Bool - ) + var wg sync.WaitGroup for _, id := range nodeIDs { if !p.isConnected(id) { @@ -120,19 +77,9 @@ func (p *HAHealthProber) ProbeOnce( Uint64(zf.NodeID, id.Uint64()). Msg("HA probe: skipping offline node") - p.forgetSession(id) - continue } - nv, ok := p.state.GetNodeByID(id) - if !ok { - continue - } - - probeSession := nv.SessionEpoch() - stable := p.markSessionStable(id, probeSession) - pingID, responseCh := p.state.RegisterPing(id) callbackURL := p.serverURL + "/machine/ping-response?id=" + pingID @@ -151,7 +98,13 @@ func (p *HAHealthProber) ProbeOnce( Dur("latency", latency). Msg("HA probe: node responded") - results.Store(id, true) + if p.state.SetNodeUnhealthy(id, false) { + dispatch(change.PolicyChange()) + + log.Info(). + Uint64(zf.NodeID, id.Uint64()). + Msg("HA probe: node recovered, recalculating primaries") + } case <-timer.C: p.state.CancelPing(pingID) @@ -164,40 +117,18 @@ func (p *HAHealthProber) ProbeOnce( return } - curr, ok := p.state.GetNodeByID(id) - if !ok { - return - } - - if curr.SessionEpoch() != probeSession { - log.Debug(). - Uint64(zf.NodeID, id.Uint64()). - Uint64("probe_session", probeSession). - Uint64("current_session", curr.SessionEpoch()). - Msg("HA probe: node reconnected during probe, skipping") - - deferred.Store(true) - - return - } - - if !stable { - log.Debug(). - Uint64(zf.NodeID, id.Uint64()). - Uint64("probe_session", probeSession). - Msg("HA probe: probe of fresh session timed out, deferring to next cycle") - - deferred.Store(true) - - return - } - log.Warn(). Uint64(zf.NodeID, id.Uint64()). Dur("timeout", p.cfg.ProbeTimeout). Msg("HA probe: node did not respond") - results.Store(id, false) + if p.state.SetNodeUnhealthy(id, true) { + dispatch(change.PolicyChange()) + + log.Info(). + Uint64(zf.NodeID, id.Uint64()). + Msg("HA probe: node unhealthy, triggering failover") + } case <-ctx.Done(): p.state.CancelPing(pingID) @@ -206,28 +137,4 @@ func (p *HAHealthProber) ProbeOnce( } wg.Wait() - - // When any probe in the cycle was deferred (fresh session or - // reconnected mid-probe), drop the whole cycle's results: a partial - // batch lets the election pick a node whose connectivity is still - // unknown. The next cycle will run with stable sessions for every - // candidate and can decide on a complete picture. - if deferred.Load() { - return - } - - healthByNode := make(map[types.NodeID]bool, results.Size()) - results.Range(func(id types.NodeID, healthy bool) bool { - healthByNode[id] = healthy - - return true - }) - - if p.state.BatchSetNodeHealth(healthByNode) { - dispatch(change.PolicyChange()) - - log.Info(). - Int("haNodes", len(healthByNode)). - Msg("HA probe: health changed, triggering failover/recovery") - } } diff --git a/hscontrol/state/maprequest.go b/hscontrol/state/maprequest.go index e8571e0ac..d8cddaa14 100644 --- a/hscontrol/state/maprequest.go +++ b/hscontrol/state/maprequest.go @@ -1,5 +1,5 @@ -// Package state provides pure functions for processing [tailcfg.MapRequest] data. -// These functions are extracted from [State.UpdateNodeFromMapRequest] to improve +// Package state provides pure functions for processing MapRequest data. +// These functions are extracted from UpdateNodeFromMapRequest to improve // testability and maintainability. package state @@ -10,19 +10,19 @@ import ( "tailscale.com/tailcfg" ) -// netInfoFromMapRequest determines the correct [tailcfg.NetInfo] to use. -// Returns the [tailcfg.NetInfo] that should be used for this request. +// netInfoFromMapRequest determines the correct NetInfo to use. +// Returns the NetInfo that should be used for this request. func netInfoFromMapRequest( nodeID types.NodeID, currentHostinfo *tailcfg.Hostinfo, reqHostinfo *tailcfg.Hostinfo, ) *tailcfg.NetInfo { - // If request has [tailcfg.NetInfo], use it + // If request has NetInfo, use it if reqHostinfo != nil && reqHostinfo.NetInfo != nil { return reqHostinfo.NetInfo } - // Otherwise, use current [tailcfg.NetInfo] if available + // Otherwise, use current NetInfo if available if currentHostinfo != nil && currentHostinfo.NetInfo != nil { log.Debug(). Caller(). @@ -33,7 +33,7 @@ func netInfoFromMapRequest( return currentHostinfo.NetInfo } - // No [tailcfg.NetInfo] available anywhere - log for debugging + // No NetInfo available anywhere - log for debugging var hostname string if reqHostinfo != nil { hostname = reqHostinfo.Hostname diff --git a/hscontrol/state/maprequest_noop_test.go b/hscontrol/state/maprequest_noop_test.go deleted file mode 100644 index 6783e540f..000000000 --- a/hscontrol/state/maprequest_noop_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package state - -import ( - "strings" - "sync/atomic" - "testing" - - "github.com/stretchr/testify/require" - "gorm.io/gorm" - "tailscale.com/tailcfg" -) - -// TestNoOpMapRequestSkipsPersist ensures an identical, no-op MapRequest does -// not issue a database UPDATE (nor the O(n) policy SetNodes scan that follows -// persistNodeToDB). The node state is unchanged, so persisting is pure waste on -// the hot map-request path. -func TestNoOpMapRequestSkipsPersist(t *testing.T) { - _, s, nodeID := persistTestSetup(t) - t.Cleanup(func() { _ = s.Close() }) - - var nodeUpdateCount atomic.Int64 - - gdb := s.DB().DB - cbName := "noop_count_node_updates" - err := gdb.Callback().Update().After("gorm:update").Register(cbName, func(tx *gorm.DB) { - if tx.Statement == nil { - return - } - - if tx.Statement.Table == "nodes" || - strings.Contains(strings.ToLower(tx.Statement.SQL.String()), "update \"nodes\"") { - nodeUpdateCount.Add(1) - } - }) - require.NoError(t, err) - t.Cleanup(func() { _ = gdb.Callback().Update().Remove(cbName) }) - - nv, ok := s.GetNodeByID(nodeID) - require.True(t, ok, "node should exist in NodeStore") - - stored := nv.AsStruct() - - req := tailcfg.MapRequest{ - NodeKey: stored.NodeKey, - DiscoKey: stored.DiscoKey, - Hostinfo: &tailcfg.Hostinfo{ - Hostname: stored.Hostname, - NetInfo: &tailcfg.NetInfo{PreferredDERP: 1}, - }, - } - - // First request establishes the Hostinfo/DERP state (expected to persist). - _, err = s.UpdateNodeFromMapRequest(nodeID, req) - require.NoError(t, err) - - nodeUpdateCount.Store(0) - - // Second request is value-identical: a no-op. - req2 := tailcfg.MapRequest{ - NodeKey: stored.NodeKey, - DiscoKey: stored.DiscoKey, - Hostinfo: &tailcfg.Hostinfo{ - Hostname: stored.Hostname, - NetInfo: &tailcfg.NetInfo{PreferredDERP: 1}, - }, - } - - _, err = s.UpdateNodeFromMapRequest(nodeID, req2) - require.NoError(t, err) - - require.Equalf(t, int64(0), nodeUpdateCount.Load(), - "no-op MapRequest should not issue any nodes-table UPDATE, got %d", - nodeUpdateCount.Load()) -} diff --git a/hscontrol/state/maprequest_test.go b/hscontrol/state/maprequest_test.go index ef3217016..8a842e494 100644 --- a/hscontrol/state/maprequest_test.go +++ b/hscontrol/state/maprequest_test.go @@ -75,7 +75,7 @@ func TestNetInfoPreservationInRegistrationFlow(t *testing.T) { nodeID := types.NodeID(1) // This test reproduces the bug in registration flows where NetInfo was lost - // because we used the wrong hostinfo reference when calling [netInfoFromMapRequest] + // because we used the wrong hostinfo reference when calling NetInfoFromMapRequest t.Run("registration_flow_bug_reproduction", func(t *testing.T) { // Simulate existing node with NetInfo (before re-registration) existingNodeHostinfo := &tailcfg.Hostinfo{ diff --git a/hscontrol/state/node_health.go b/hscontrol/state/node_health.go deleted file mode 100644 index 4edaf3986..000000000 --- a/hscontrol/state/node_health.go +++ /dev/null @@ -1,92 +0,0 @@ -package state - -import ( - "fmt" - - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util/zlog/zf" - "github.com/rs/zerolog/log" -) - -// nodeHealthCheck names a class of stored-node-data defect that breaks normal -// operation and explains how to fix it. ok == true means the node passes the -// check. This is the extension point for node-data validation: add a check -// here as new corrupt-data classes surface (nil hostinfo, invalid IPs, -// tags-XOR-user violations, ...) and both the boot scan and any future caller -// run the whole set. -type nodeHealthCheck struct { - name string - check func(nv types.NodeView, cfg *types.Config) (problem, fixHint string, ok bool) -} - -// nodeHealthChecks is the registry of node-data health checks. Today it carries -// the one issue #3346 needs; append to it rather than reshaping callers. -var nodeHealthChecks = []nodeHealthCheck{givenNameMapsToValidFQDN} - -// givenNameMapsToValidFQDN flags a node whose stored GivenName cannot produce a -// valid FQDN (empty, or longer than MaxHostnameLength once base_domain is -// applied). Such a node cannot be rendered into a netmap — neither its own nor -// any peer's — so it must be renamed to recover. -var givenNameMapsToValidFQDN = nodeHealthCheck{ - name: "given-name-maps-to-valid-fqdn", - check: func(nv types.NodeView, cfg *types.Config) (string, string, bool) { - err := types.ValidateGivenName(nv.GivenName(), cfg.BaseDomain) - if err != nil { - return err.Error(), fmt.Sprintf("headscale nodes rename %d ", nv.ID()), false - } - - return "", "", true - }, -} - -// nodeHealthFinding is a single failed check for a single node. -type nodeHealthFinding struct { - nodeID types.NodeID - hostname string - check string - problem string - fixHint string -} - -// scanNodeHealth runs every registered check against every node in the store -// and returns one finding per failure. It only reports — it never mutates a -// node — so an operator can repair the underlying data without the server -// silently rewriting a user-visible name. -func (s *State) scanNodeHealth() []nodeHealthFinding { - var findings []nodeHealthFinding - - for _, nv := range s.nodeStore.ListNodes().All() { - for _, c := range nodeHealthChecks { - problem, fixHint, ok := c.check(nv, s.cfg) - if ok { - continue - } - - findings = append(findings, nodeHealthFinding{ - nodeID: nv.ID(), - hostname: nv.Hostname(), - check: c.name, - problem: problem, - fixHint: fixHint, - }) - } - } - - return findings -} - -// logNodeHealth scans the store once and logs an actionable warning per -// finding. Called at startup so an operator learns — by node id and fix -// command — about stored data that will break map generation, without the -// server changing anything itself. -func (s *State) logNodeHealth() { - for _, f := range s.scanNodeHealth() { - log.Warn(). - Uint64(zf.NodeID, f.nodeID.Uint64()). - Str(zf.NodeHostname, f.hostname). - Str("check", f.check). - Str("problem", f.problem). - Str("fix", f.fixHint). - Msg("node has invalid data that breaks map generation; rename it to restore connectivity") - } -} diff --git a/hscontrol/state/node_health_test.go b/hscontrol/state/node_health_test.go deleted file mode 100644 index cd61a25b8..000000000 --- a/hscontrol/state/node_health_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package state - -import ( - "testing" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/stretchr/testify/require" -) - -func TestGivenNameMapsToValidFQDNCheck(t *testing.T) { - cfg := &types.Config{BaseDomain: "example.com"} - - _, _, ok := givenNameMapsToValidFQDN.check((&types.Node{ID: 1, GivenName: "valid"}).View(), cfg) - require.True(t, ok, "a valid given name must pass the check") - - problem, fixHint, ok := givenNameMapsToValidFQDN.check((&types.Node{ID: 7, GivenName: ""}).View(), cfg) - require.False(t, ok, "an empty given name must fail the check") - require.NotEmpty(t, problem) - require.Contains(t, fixHint, "rename 7", "fix hint must name the offending node") -} - -// TestScanNodeHealthReportsInvalidNameWithoutMutating proves the boot scan -// reports a node whose stored name would break map generation (issue #3346) -// with an actionable fix, and that it never rewrites the stored name — the -// maintainer's decision is log-only, no silent mutation. -func TestScanNodeHealthReportsInvalidNameWithoutMutating(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("scan-user") - bad := database.CreateRegisteredNodeForTest(user, "scan-bad") - good := database.CreateRegisteredNodeForTest(user, "scan-good") - - require.NoError(t, database.DB. - Model(&types.Node{}). - Where("id = ?", bad.ID). - Update("given_name", "").Error) - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - findings := s.scanNodeHealth() - - var badFinding *nodeHealthFinding - - for i := range findings { - require.NotEqual(t, good.ID, findings[i].nodeID, "a valid node must not be reported") - - if findings[i].nodeID == bad.ID { - badFinding = &findings[i] - } - } - - require.NotNil(t, badFinding, "a node with an invalid name must be reported") - require.Contains(t, badFinding.fixHint, "rename", "finding must carry an actionable fix") - - // Log-only: neither the scan nor boot may rewrite the stored name. - nv, ok := s.GetNodeByID(bad.ID) - require.True(t, ok) - require.Empty(t, nv.GivenName(), "boot scan must not mutate the stored name") -} diff --git a/hscontrol/state/node_store.go b/hscontrol/state/node_store.go index cece54edd..a2066f15a 100644 --- a/hscontrol/state/node_store.go +++ b/hscontrol/state/node_store.go @@ -8,7 +8,6 @@ import ( "slices" "strconv" "strings" - "sync" "sync/atomic" "time" @@ -22,12 +21,12 @@ import ( ) // fallbackGivenName is the DNS label used when a node is written with -// an empty [types.Node.GivenName]. Matches Tailscale SaaS behaviour -// for empty sanitised labels. +// an empty GivenName. Matches Tailscale SaaS behaviour for empty +// sanitised labels. const fallbackGivenName = "node" -// Errors returned by [NodeStore.SetGivenName]. [ErrNodeNotFound] is defined -// in state.go and reused here. +// Errors returned by SetGivenName. ErrNodeNotFound is defined in +// state.go and reused here. var ( ErrGivenNameTaken = errors.New("given name already in use by another node") ErrGivenNameInvalid = errors.New("given name is not a valid DNS label") @@ -36,9 +35,9 @@ var ( const ( put = 1 del = 2 + update = 3 rebuildPeerMaps = 4 setName = 5 - updateMulti = 6 ) const prometheusNamespace = "headscale" @@ -108,12 +107,6 @@ type NodeStore struct { peersFunc PeersFunc writeQueue chan work - // stopped is closed once by Stop to signal the writer goroutine to exit - // and to let in-flight writes return cleanly instead of panicking with - // "send on closed channel" during shutdown. - stopped chan struct{} - stopOnce sync.Once - batchSize int batchTimeout time.Duration } @@ -130,7 +123,6 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc peersFunc: peersFunc, batchSize: batchSize, batchTimeout: batchTimeout, - stopped: make(chan struct{}), } store.data.Store(&snap) @@ -140,10 +132,10 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc return store } -// Snapshot is the representation of the current state of the [NodeStore]. +// Snapshot is the representation of the current state of the NodeStore. // It contains all nodes and their relationships. // It is a copy-on-write structure, meaning that when a write occurs, -// a new [Snapshot] is created with the updated state, +// a new Snapshot is created with the updated state, // and replaces the old one atomically. type Snapshot struct { // nodesByID is the main source of truth for nodes. @@ -169,23 +161,19 @@ type Snapshot struct { // based on the current policy. type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeView -// work represents a single operation to be performed on the [NodeStore]. +// work represents a single operation to be performed on the NodeStore. type work struct { op int nodeID types.NodeID node types.Node + updateFn UpdateNodeFunc result chan struct{} - nodeResult chan types.NodeView + nodeResult chan types.NodeView // Channel to return the resulting node after batch application // For rebuildPeerMaps operation rebuildResult chan struct{} // For setName operation (admin rename, reject-on-collision path). name string errResult chan error - // For updateMulti: per-node update functions applied as a single - // batch entry so callers that need an atomic election (e.g. the HA - // prober applying multiple probe results at once) cannot have a - // partial snapshot published between the updates. - multiUpdates map[types.NodeID]UpdateNodeFunc } // PutNode adds or updates a node in the store. @@ -207,13 +195,7 @@ func (s *NodeStore) PutNode(n types.Node) types.NodeView { nodeStoreQueueDepth.Inc() - select { - case s.writeQueue <- work: - case <-s.stopped: - nodeStoreQueueDepth.Dec() - - return types.NodeView{} - } + s.writeQueue <- work <-work.result nodeStoreQueueDepth.Dec() @@ -225,65 +207,48 @@ func (s *NodeStore) PutNode(n types.Node) types.NodeView { return resultNode } -// UpdateNodeFunc is a function type that takes a pointer to a [types.Node] and modifies it. +// UpdateNodeFunc is a function type that takes a pointer to a Node and modifies it. type UpdateNodeFunc func(n *types.Node) -// UpdateNode applies a function to modify a specific node in the -// store. Single-node convenience wrapper around [NodeStore.UpdateNodes] -// — the writer goroutine signals completion only after the post-batch -// snapshot has been stored, so the follow-up [NodeStore.GetNode] read -// sees the applied update. Returns the resulting node and whether it -// exists. +// UpdateNode applies a function to modify a specific node in the store. +// This is a blocking operation that waits for the write to complete. +// This is analogous to a database "transaction", or, the caller should +// rather collect all data they want to change, and then call this function. +// Fewer calls are better. +// Returns the resulting node after all modifications in the batch have been applied. // -// Callers that need to change several nodes atomically should call -// [NodeStore.UpdateNodes] directly; collecting changes into one batch -// keeps the election from running on a half-applied snapshot. -func (s *NodeStore) UpdateNode(nodeID types.NodeID, updateFn UpdateNodeFunc) (types.NodeView, bool) { +// TODO(kradalby): Technically we could have a version of this that modifies the node +// in the current snapshot if _we know_ that the change will not affect the peer relationships. +// This is because the main nodesByID map contains the struct, and every other map is using a +// pointer to the underlying struct. The gotcha with this is that we will need to introduce +// a lock around the nodesByID map to ensure that no other writes are happening +// while we are modifying the node. Which mean we would need to implement read-write locks +// on all read operations. +func (s *NodeStore) UpdateNode(nodeID types.NodeID, updateFn func(n *types.Node)) (types.NodeView, bool) { timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("update")) defer timer.ObserveDuration() - s.UpdateNodes(map[types.NodeID]UpdateNodeFunc{nodeID: updateFn}) - - nodeStoreOperations.WithLabelValues("update").Inc() - - return s.GetNode(nodeID) -} - -// UpdateNodes applies per-node update functions in a single atomic -// batch. The election that recomputes primary routes runs once, after -// every update has landed, so callers cannot observe an intermediate -// snapshot where only some of the updates are visible. Use this when -// the order in which two writers' updates are individually published -// would change the election outcome — e.g. the HA prober applying -// concurrent probe-timeout results. -func (s *NodeStore) UpdateNodes(updates map[types.NodeID]UpdateNodeFunc) { - timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("update_multi")) - defer timer.ObserveDuration() - - if len(updates) == 0 { - return - } - - w := work{ - op: updateMulti, - multiUpdates: updates, - result: make(chan struct{}), + work := work{ + op: update, + nodeID: nodeID, + updateFn: updateFn, + result: make(chan struct{}), + nodeResult: make(chan types.NodeView, 1), } nodeStoreQueueDepth.Inc() - select { - case s.writeQueue <- w: - case <-s.stopped: - nodeStoreQueueDepth.Dec() + s.writeQueue <- work - return - } - - <-w.result + <-work.result nodeStoreQueueDepth.Dec() - nodeStoreOperations.WithLabelValues("update_multi").Inc() + resultNode := <-work.nodeResult + + nodeStoreOperations.WithLabelValues("update").Inc() + + // Return the node and whether it exists (is valid) + return resultNode, resultNode.Valid() } // DeleteNode removes a node from the store by its ID. @@ -300,13 +265,7 @@ func (s *NodeStore) DeleteNode(id types.NodeID) { nodeStoreQueueDepth.Inc() - select { - case s.writeQueue <- work: - case <-s.stopped: - nodeStoreQueueDepth.Dec() - - return - } + s.writeQueue <- work <-work.result nodeStoreQueueDepth.Dec() @@ -314,20 +273,19 @@ func (s *NodeStore) DeleteNode(id types.NodeID) { nodeStoreOperations.WithLabelValues("delete").Inc() } -// SetGivenName sets [types.Node.GivenName] on the node identified by id, +// SetGivenName sets node.GivenName on the node identified by id, // rejecting the write if the name is already held by another node. // Intended for the admin rename path, where auto-bumping a // user-supplied name would be surprising. // // Returns: -// - the stored [types.NodeView] and nil on success -// - [ErrGivenNameInvalid] if name is not a valid DNS label -// - [ErrGivenNameTaken] if another node already holds name -// - [ErrNodeNotFound] if no node with id exists +// - the stored NodeView and nil on success +// - ErrGivenNameInvalid if name is not a valid DNS label +// - ErrGivenNameTaken if another node already holds name +// - ErrNodeNotFound if no node with id exists // -// Runs as a single writer-goroutine op, so the uniqueness check and the -// write are atomic with respect to concurrent -// [NodeStore.PutNode]/[NodeStore.UpdateNode]. +// Runs as a single writer-goroutine op, so the uniqueness check and +// the write are atomic with respect to concurrent PutNode/UpdateNode. func (s *NodeStore) SetGivenName(id types.NodeID, name string) (types.NodeView, error) { timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("set_name")) defer timer.ObserveDuration() @@ -343,13 +301,7 @@ func (s *NodeStore) SetGivenName(id types.NodeID, name string) (types.NodeView, nodeStoreQueueDepth.Inc() - select { - case s.writeQueue <- w: - case <-s.stopped: - nodeStoreQueueDepth.Dec() - - return types.NodeView{}, nil - } + s.writeQueue <- w <-w.result nodeStoreQueueDepth.Dec() @@ -364,19 +316,15 @@ func (s *NodeStore) SetGivenName(id types.NodeID, name string) (types.NodeView, return <-w.nodeResult, nil } -// Start initializes the [NodeStore] and starts processing the write queue. +// Start initializes the NodeStore and starts processing the write queue. func (s *NodeStore) Start() { s.writeQueue = make(chan work) go s.processWrite() } -// Stop stops the [NodeStore]. It signals the writer goroutine via stopped -// rather than closing writeQueue, so writes racing shutdown drop cleanly -// instead of panicking on a closed channel. +// Stop stops the NodeStore. func (s *NodeStore) Stop() { - s.stopOnce.Do(func() { - close(s.stopped) - }) + close(s.writeQueue) } // processWrite processes the write queue in batches. @@ -388,7 +336,16 @@ func (s *NodeStore) processWrite() { for { select { - case w := <-s.writeQueue: + case w, ok := <-s.writeQueue: + if !ok { + // Channel closed, apply any remaining batch and exit + if len(batch) != 0 { + s.applyBatch(batch) + } + + return + } + batch = append(batch, w) if len(batch) >= s.batchSize { s.applyBatch(batch) @@ -403,14 +360,6 @@ func (s *NodeStore) processWrite() { } c.Reset(s.batchTimeout) - case <-s.stopped: - // Apply any remaining batch so in-flight writers receive their - // results, then exit. - if len(batch) != 0 { - s.applyBatch(batch) - } - - return } } } @@ -456,21 +405,20 @@ func (s *NodeStore) applyBatch(batch []work) { if w.nodeResult != nil { nodeResultRequests[w.nodeID] = append(nodeResultRequests[w.nodeID], w) } - case updateMulti: - for id, fn := range w.multiUpdates { - n, exists := nodes[id] - if !exists { - continue - } - + case update: + // Update the specific node identified by nodeID + if n, exists := nodes[w.nodeID]; exists { oldGivenName := n.GivenName - fn(&n) + w.updateFn(&n) if n.GivenName != oldGivenName { n.GivenName = resolveGivenName(nodes, n.ID, n.GivenName) } + nodes[w.nodeID] = n + } - nodes[id] = n + if w.nodeResult != nil { + nodeResultRequests[w.nodeID] = append(nodeResultRequests[w.nodeID], w) } case del: delete(nodes, w.nodeID) @@ -527,24 +475,33 @@ func (s *NodeStore) applyBatch(batch []work) { // Update node count gauge nodeStoreNodesCount.Set(float64(len(nodes))) - // Send the resulting nodes to all work items that requested them. - // A zero-value NodeView{} reports Valid()==false, matching node.View() - // for a node that was deleted or never existed. + // Send the resulting nodes to all work items that requested them for nodeID, workItems := range nodeResultRequests { - var nodeView types.NodeView if node, exists := nodes[nodeID]; exists { - nodeView = node.View() - } + nodeView := node.View() + for _, w := range workItems { + w.nodeResult <- nodeView - for _, w := range workItems { - w.nodeResult <- nodeView + close(w.nodeResult) - close(w.nodeResult) + if w.errResult != nil { + w.errResult <- setErrResults[w] - if w.errResult != nil { - w.errResult <- setErrResults[w] + close(w.errResult) + } + } + } else { + // Node was deleted or doesn't exist + for _, w := range workItems { + w.nodeResult <- types.NodeView{} // Send invalid view - close(w.errResult) + close(w.nodeResult) + + if w.errResult != nil { + w.errResult <- setErrResults[w] + + close(w.errResult) + } } } } @@ -564,14 +521,14 @@ func (s *NodeStore) applyBatch(batch []work) { // resolveGivenName returns a unique DNS label for the node identified // by self, based on the caller-supplied base label. If base is empty -// it falls back to [fallbackGivenName] ("node"). The label's own holder +// it falls back to fallbackGivenName ("node"). The label's own holder // (self) is excluded from the collision scan so an idempotent write // keeps the current label. // // On collision the label is bumped as base, base-1, base-2, …, first -// unused wins. Must be called from the [NodeStore] writer goroutine -// (inside [NodeStore.applyBatch]) so the nodes map reflects all earlier -// ops in the batch and no other writer can interleave. +// unused wins. Must be called from the NodeStore writer goroutine +// (inside applyBatch) so the nodes map reflects all earlier ops in +// the batch and no other writer can interleave. func resolveGivenName(nodes map[types.NodeID]types.Node, self types.NodeID, base string) string { if base == "" { base = fallbackGivenName @@ -597,7 +554,7 @@ func resolveGivenName(nodes map[types.NodeID]types.Node, self types.NodeID, base } // snapshotFromNodes builds the index maps and primary-route table for -// a new [Snapshot]. prevRoutes carries forward the previous primary +// a new Snapshot. prevRoutes carries forward the previous primary // assignment so a still-valid choice survives unrelated batches. func snapshotFromNodes( nodes map[types.NodeID]types.Node, @@ -660,13 +617,26 @@ func snapshotFromNodes( return newSnap } -// onlineAdvertisers maps each non-exit prefix to the IDs of online nodes -// that approve it. Nodes are visited in the order of ids, so the per-prefix -// slices preserve that order. -func onlineAdvertisers( +// electPrimaryRoutes picks the primary advertiser for each non-exit +// prefix. The previous primary is preserved when it is still online +// and healthy (anti-flap); otherwise the lowest-NodeID healthy +// advertiser wins. When every advertiser is unhealthy the previous +// primary is preserved if still a candidate, falling back to the +// lowest-NodeID candidate so peers see *some* primary instead of +// none. Anti-flap in the all-unhealthy case matters under cable-pull +// where IsOnline lags reality and a naive lowest-ID fallback churns +// primaries to a node that is itself unreachable (issue #3203). +func electPrimaryRoutes( nodes map[types.NodeID]types.Node, - ids []types.NodeID, -) map[netip.Prefix][]types.NodeID { + prev map[netip.Prefix]types.NodeID, +) (map[netip.Prefix]types.NodeID, map[types.NodeID]bool) { + ids := make([]types.NodeID, 0, len(nodes)) + for id := range nodes { + ids = append(ids, id) + } + + slices.Sort(ids) + advertisers := make(map[netip.Prefix][]types.NodeID) for _, id := range ids { @@ -684,24 +654,6 @@ func onlineAdvertisers( } } - return advertisers -} - -// electPrimaryRoutes picks the primary advertiser for each non-exit -// prefix. Inputs are restricted to online nodes that advertise the -// prefix. The previous primary is preserved when it is still online -// and healthy (anti-flap); otherwise the lowest-NodeID healthy -// advertiser wins. When every advertiser is unhealthy the previous -// primary is preserved only if still a candidate — falling back to -// any other candidate would point peers at a node the prober has -// already declared unreachable, so leaving the prefix unmapped is -// preferred until a probe cycle finds one that responds. -func electPrimaryRoutes( - nodes map[types.NodeID]types.Node, - prev map[netip.Prefix]types.NodeID, -) (map[netip.Prefix]types.NodeID, map[types.NodeID]bool) { - advertisers := onlineAdvertisers(nodes, slices.Sorted(maps.Keys(nodes))) - routes := make(map[netip.Prefix]types.NodeID, len(advertisers)) for prefix, candidates := range advertisers { if cur, ok := prev[prefix]; ok && @@ -725,16 +677,14 @@ func electPrimaryRoutes( } } - // All-unhealthy fallback: preserve the previous primary only - // when it is still a candidate. Falling back to any candidate - // would point peers at a node the prober has already declared - // unreachable; leaving the prefix unmapped is honest until a - // probe cycle picks one that responds. - if !found { + if !found && len(candidates) >= 1 { if cur, ok := prev[prefix]; ok && slices.Contains(candidates, cur) { selected = cur - found = true + } else { + selected = candidates[0] } + + found = true } if found { @@ -752,8 +702,8 @@ func electPrimaryRoutes( // GetNode retrieves a node by its ID. // The bool indicates if the node exists or is available (like "err not found"). -// The [types.NodeView] might be invalid, so it must be checked with .Valid(), which must -// be used to ensure it isn't an invalid node (this is more of a node error or node is broken). +// The NodeView might be invalid, so it must be checked with .Valid(), which must be used to ensure +// it isn't an invalid node (this is more of a node error or node is broken). func (s *NodeStore) GetNode(id types.NodeID) (types.NodeView, bool) { timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get")) defer timer.ObserveDuration() @@ -768,10 +718,10 @@ func (s *NodeStore) GetNode(id types.NodeID) (types.NodeView, bool) { return n.View(), true } -// GetNodeByNodeKey retrieves a node by its [key.NodePublic]. +// GetNodeByNodeKey retrieves a node by its NodeKey. // The bool indicates if the node exists or is available (like "err not found"). -// The [types.NodeView] might be invalid, so it must be checked with .Valid(), which must -// be used to ensure it isn't an invalid node (this is more of a node error or node is broken). +// The NodeView might be invalid, so it must be checked with .Valid(), which must be used to ensure +// it isn't an invalid node (this is more of a node error or node is broken). func (s *NodeStore) GetNodeByNodeKey(nodeKey key.NodePublic) (types.NodeView, bool) { timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get_by_key")) defer timer.ObserveDuration() @@ -783,30 +733,47 @@ func (s *NodeStore) GetNodeByNodeKey(nodeKey key.NodePublic) (types.NodeView, bo return nodeView, exists } -// GetNodesByMachineKeyAllUsers returns every node sharing machineKey, keyed by -// owning UserID. Tagged nodes are indexed under UserID(0) (the tagged sentinel); -// user-owned nodes under their owning UserID. Returns an empty map if none. -// -// One machine key can map to several nodes (the same device registered by -// different users via the "create new, do not transfer" path). Exposing the -// whole set lets callers decide with full context — index [userID] for an exact -// match, [0] for a tagged node, or reject when the set is ambiguous — rather -// than guessing from a single arbitrary pick. -func (s *NodeStore) GetNodesByMachineKeyAllUsers(machineKey key.MachinePublic) map[types.UserID]types.NodeView { - timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get_nodes_by_machine_key_all_users")) +// GetNodeByMachineKey returns a node by its machine key and user ID. The bool indicates if the node exists. +func (s *NodeStore) GetNodeByMachineKey(machineKey key.MachinePublic, userID types.UserID) (types.NodeView, bool) { + timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get_by_machine_key")) defer timer.ObserveDuration() - nodeStoreOperations.WithLabelValues("get_nodes_by_machine_key_all_users").Inc() + nodeStoreOperations.WithLabelValues("get_by_machine_key").Inc() - userMap := s.data.Load().nodesByMachineKey[machineKey] + snapshot := s.data.Load() + if userMap, exists := snapshot.nodesByMachineKey[machineKey]; exists { + if node, exists := userMap[userID]; exists { + return node, true + } + } - out := make(map[types.UserID]types.NodeView, len(userMap)) - maps.Copy(out, userMap) - - return out + return types.NodeView{}, false } -// DebugString returns debug information about the [NodeStore]. +// GetNodeByMachineKeyAnyUser returns the first node with the given machine key, +// regardless of which user it belongs to. This is useful for scenarios like +// transferring a node to a different user when re-authenticating with a +// different user's auth key. +// If multiple nodes exist with the same machine key (different users), the +// first one found is returned (order is not guaranteed). +func (s *NodeStore) GetNodeByMachineKeyAnyUser(machineKey key.MachinePublic) (types.NodeView, bool) { + timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get_by_machine_key_any_user")) + defer timer.ObserveDuration() + + nodeStoreOperations.WithLabelValues("get_by_machine_key_any_user").Inc() + + snapshot := s.data.Load() + if userMap, exists := snapshot.nodesByMachineKey[machineKey]; exists { + // Return the first node found (order not guaranteed due to map iteration) + for _, node := range userMap { + return node, true + } + } + + return types.NodeView{}, false +} + +// DebugString returns debug information about the NodeStore. func (s *NodeStore) DebugString() string { snapshot := s.data.Load() @@ -915,10 +882,21 @@ func (s *NodeStore) PrimaryRoutesForNode(id types.NodeID) []netip.Prefix { func (s *NodeStore) HANodes() map[netip.Prefix][]types.NodeID { snap := s.data.Load() - advertisers := onlineAdvertisers( - snap.nodesByID, - slices.Sorted(maps.Keys(snap.nodesByID)), - ) + advertisers := make(map[netip.Prefix][]types.NodeID) + + for id, n := range snap.nodesByID { + if n.IsOnline == nil || !*n.IsOnline { + continue + } + + for _, p := range n.AllApprovedRoutes() { + if tsaddr.IsExitRoute(p) { + continue + } + + advertisers[p] = append(advertisers[p], id) + } + } out := make(map[netip.Prefix][]types.NodeID) @@ -927,6 +905,7 @@ func (s *NodeStore) HANodes() map[netip.Prefix][]types.NodeID { continue } + slices.Sort(ids) out[p] = ids } @@ -975,8 +954,8 @@ func (s *NodeStore) PrimaryRoutesString() string { return b.String() } -// RebuildPeerMaps rebuilds the peer relationship map using the current [PeersFunc]. -// This must be called after policy changes because [PeersFunc] uses [policy.PolicyManager]'s +// RebuildPeerMaps rebuilds the peer relationship map using the current peersFunc. +// This must be called after policy changes because peersFunc uses PolicyManager's // filters to determine which nodes can see each other. Without rebuilding, the // peer map would use stale filter data until the next node add/delete. func (s *NodeStore) RebuildPeerMaps() { diff --git a/hscontrol/state/node_store_stop_test.go b/hscontrol/state/node_store_stop_test.go deleted file mode 100644 index 219d14e70..000000000 --- a/hscontrol/state/node_store_stop_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package state - -import ( - "sync" - "testing" - - "github.com/juanfont/headscale/hscontrol/types" -) - -// TestNodeStoreWriteDuringStopNoPanic ensures a write racing with Stop does not -// panic with "send on closed channel". During graceful shutdown a grace-period -// Disconnect (or a scheduled HA probe result) can still issue a NodeStore write -// after Stop has run; that write must be either applied or cleanly dropped, -// never crash the process. -func TestNodeStoreWriteDuringStopNoPanic(t *testing.T) { - const iterations = 200 - - for range iterations { - store := NewNodeStore(nil, allowAllPeersFunc, TestBatchSize, TestBatchTimeout) - store.Start() - - var wg sync.WaitGroup - - wg.Go(func() { - node := createConcurrentTestNode(types.NodeID(1), "grace-period-node") - store.PutNode(node) - }) - - wg.Go(func() { - store.Stop() - }) - - wg.Wait() - } -} diff --git a/hscontrol/state/node_store_test.go b/hscontrol/state/node_store_test.go index 04d4290bb..6b4217540 100644 --- a/hscontrol/state/node_store_test.go +++ b/hscontrol/state/node_store_test.go @@ -684,7 +684,7 @@ func TestNodeStoreOperations(t *testing.T) { finalNode := snapshot.nodesByID[1] assert.Equal(t, "multi-update-hostname", finalNode.Hostname) assert.Equal(t, "multi-update-givenname", finalNode.GivenName) - assert.Equal(t, []string{"tag1", "tag2"}, finalNode.Tags.List()) + assert.Equal(t, []string{"tag1", "tag2"}, finalNode.Tags) }, }, }, @@ -722,14 +722,14 @@ func TestNodeStoreOperations(t *testing.T) { assert.NotNil(t, nodePtr) assert.Equal(t, "db-save-hostname", nodePtr.Hostname) assert.Equal(t, "db-save-given", nodePtr.GivenName) - assert.Equal(t, []string{"db-tag1", "db-tag2"}, nodePtr.Tags.List()) + assert.Equal(t, []string{"db-tag1", "db-tag2"}, nodePtr.Tags) // Verify the snapshot also reflects the same state snapshot := store.data.Load() storedNode := snapshot.nodesByID[1] assert.Equal(t, "db-save-hostname", storedNode.Hostname) assert.Equal(t, "db-save-given", storedNode.GivenName) - assert.Equal(t, []string{"db-tag1", "db-tag2"}, storedNode.Tags.List()) + assert.Equal(t, []string{"db-tag1", "db-tag2"}, storedNode.Tags) }, }, { @@ -792,15 +792,15 @@ func TestNodeStoreOperations(t *testing.T) { // All should have the complete final state assert.Equal(t, "concurrent-db-hostname", nodePtr1.Hostname) assert.Equal(t, "concurrent-db-given", nodePtr1.GivenName) - assert.Equal(t, []string{"concurrent-tag"}, nodePtr1.Tags.List()) + assert.Equal(t, []string{"concurrent-tag"}, nodePtr1.Tags) assert.Equal(t, "concurrent-db-hostname", nodePtr2.Hostname) assert.Equal(t, "concurrent-db-given", nodePtr2.GivenName) - assert.Equal(t, []string{"concurrent-tag"}, nodePtr2.Tags.List()) + assert.Equal(t, []string{"concurrent-tag"}, nodePtr2.Tags) assert.Equal(t, "concurrent-db-hostname", nodePtr3.Hostname) assert.Equal(t, "concurrent-db-given", nodePtr3.GivenName) - assert.Equal(t, []string{"concurrent-tag"}, nodePtr3.Tags.List()) + assert.Equal(t, []string{"concurrent-tag"}, nodePtr3.Tags) // Verify consistency with stored state snapshot := store.data.Load() @@ -1321,63 +1321,3 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) { assert.Equal(t, 1, peers1.Len(), "ListPeers for node1 should return 1") assert.Equal(t, 1, peers2.Len(), "ListPeers for node2 should return 1") } - -// TestGetNodesByMachineKeyAllUsers ensures the lookup returns every node sharing -// a machine key keyed by owning UserID (tagged nodes under UserID(0)), so callers -// see the full set instead of a single arbitrary pick. -func TestGetNodesByMachineKeyAllUsers(t *testing.T) { - mk := key.NewMachine().Public() - - t.Run("empty when absent", func(t *testing.T) { - store := NewNodeStore(nil, allowAllPeersFunc, TestBatchSize, TestBatchTimeout) - - store.Start() - defer store.Stop() - - require.Empty(t, store.GetNodesByMachineKeyAllUsers(mk)) - }) - - t.Run("returns all user-owned nodes keyed by user", func(t *testing.T) { - store := NewNodeStore(nil, allowAllPeersFunc, TestBatchSize, TestBatchTimeout) - - store.Start() - defer store.Stop() - - n1 := createTestNode(1, 1, "user1", "node1") - n1.MachineKey = mk - n2 := createTestNode(2, 2, "user2", "node2") - n2.MachineKey = mk - - store.PutNode(n1) - store.PutNode(n2) - - all := store.GetNodesByMachineKeyAllUsers(mk) - require.Len(t, all, 2) - require.Equal(t, types.NodeID(1), all[types.UserID(1)].ID()) - require.Equal(t, types.NodeID(2), all[types.UserID(2)].ID()) - }) - - t.Run("tagged node indexed under UserID(0)", func(t *testing.T) { - store := NewNodeStore(nil, allowAllPeersFunc, TestBatchSize, TestBatchTimeout) - - store.Start() - defer store.Stop() - - owned := createTestNode(1, 1, "user1", "node1") - owned.MachineKey = mk - tagged := createTestNode(3, 3, "user3", "node3") - tagged.MachineKey = mk - tagged.UserID = nil - tagged.User = nil - tagged.Tags = []string{"tag:foo"} - - store.PutNode(owned) - store.PutNode(tagged) - - all := store.GetNodesByMachineKeyAllUsers(mk) - require.Len(t, all, 2) - require.Equal(t, types.NodeID(1), all[types.UserID(1)].ID()) - require.True(t, all[types.UserID(0)].IsTagged()) - require.Equal(t, types.NodeID(3), all[types.UserID(0)].ID()) - }) -} diff --git a/hscontrol/state/persist_clobber_test.go b/hscontrol/state/persist_clobber_test.go deleted file mode 100644 index b2ec961dd..000000000 --- a/hscontrol/state/persist_clobber_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package state - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "tailscale.com/tailcfg" -) - -// TestPersistNodeDoesNotClobberConcurrentAdminWrite ensures that persisting a -// node snapshot captured earlier (as UpdateNodeFromMapRequest does at the top -// of its body) cannot overwrite a concurrent admin write (SetNodeTags) that -// landed in between. NodeStore is the source of truth; the database row must -// converge on it rather than reverting to the stale snapshot. -func TestPersistNodeDoesNotClobberConcurrentAdminWrite(t *testing.T) { - dbPath, s, nodeID := persistTestSetup(t) - - pol := `{ - "tagOwners": {"tag:foo": ["persist-user@"]}, - "acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}] - }` - _, err := s.SetPolicy([]byte(pol)) - require.NoError(t, err) - - before, ok := s.GetNodeByID(nodeID) - require.True(t, ok) - require.False(t, before.IsTagged(), "node should start user-owned") - require.True(t, before.UserID().Valid(), "node should start with a UserID") - - // (1) Map-request captures the node snapshot (the stale view). - staleView, ok := s.nodeStore.GetNode(nodeID) - require.True(t, ok) - - staleNode := staleView.AsStruct() - staleNode.Hostinfo = &tailcfg.Hostinfo{Hostname: "persist-node"} - staleView = staleNode.View() - - // (2) Concurrent admin SetNodeTags lands in the window: NodeStore + DB - // become tagged and user ownership is cleared. - _, _, err = s.SetNodeTags(nodeID, []string{"tag:foo"}) - require.NoError(t, err) - - dbAfterAdmin, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - require.Equal(t, []string{"tag:foo"}, dbAfterAdmin.Tags.List(), - "precondition: admin SetNodeTags must have written the tag to the DB") - - // (3) Map-request persists its stale snapshot. - _, _, err = s.persistNodeToDB(staleView) - require.NoError(t, err) - - // The admin write must survive. - dbFinal, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - assert.Equal(t, []string{"tag:foo"}, dbFinal.Tags.List(), - "DB tags must reflect the admin SetNodeTags, not the stale persist") - assert.Nil(t, dbFinal.UserID, - "DB UserID must stay nil after tagging (tags XOR user ownership)") - - // Restart: the divergence would surface here in production. - require.NoError(t, s.Close()) - s2 := persistTestReopen(t, dbPath) - - reloaded, ok := s2.GetNodeByID(nodeID) - require.True(t, ok, "node should reload from DB after restart") - assert.True(t, reloaded.IsTagged(), - "after restart the node must still be tagged") - assert.Equal(t, []string{"tag:foo"}, reloaded.AsStruct().Tags.List(), - "after restart the node must still carry tag:foo") -} diff --git a/hscontrol/state/persist_test.go b/hscontrol/state/persist_test.go deleted file mode 100644 index 0be0b4115..000000000 --- a/hscontrol/state/persist_test.go +++ /dev/null @@ -1,584 +0,0 @@ -package state - -import ( - "errors" - "net/netip" - "sync" - "testing" - "time" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/juanfont/headscale/hscontrol/types" - "github.com/juanfont/headscale/hscontrol/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gorm.io/gorm" - "tailscale.com/tailcfg" - "tailscale.com/types/key" -) - -// persistTestSetup pre-creates a sqlite database on disk with a single -// registered node, then constructs a State that loads it. The on-disk -// path is returned so the test can close the State and re-open one -// against the same file to simulate a server restart. The caller owns -// the returned State and must Close it; persistTestReopen handles the -// second State's lifecycle for the restart simulation. -func persistTestSetup(t *testing.T) (string, *State, types.NodeID) { - t.Helper() - - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("persist-user") - node := database.CreateRegisteredNodeForTest(user, "persist-node") - - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - - return dbPath, s, node.ID -} - -// persistTestReopen constructs a fresh State pointed at the same -// sqlite file and registers a cleanup to close it at the end of the -// test. Use it to simulate a server restart after the first State has -// been explicitly closed by the caller. -func persistTestReopen(t *testing.T, dbPath string) *State { - t.Helper() - - s, err := NewState(persistTestConfig(dbPath)) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - return s -} - -func persistTestConfig(dbPath string) *types.Config { - prefixV4 := netip.MustParsePrefix("100.64.0.0/10") - prefixV6 := netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - return &types.Config{ - Database: types.DatabaseConfig{ - Type: types.DatabaseSqlite, - Sqlite: types.SqliteConfig{ - Path: dbPath, - }, - }, - PrefixV4: &prefixV4, - PrefixV6: &prefixV6, - IPAllocation: types.IPAllocationStrategySequential, - BaseDomain: "headscale.test", - Policy: types.PolicyConfig{ - Mode: types.PolicyModeDB, - }, - Tuning: types.Tuning{ - NodeStoreBatchSize: TestBatchSize, - NodeStoreBatchTimeout: TestBatchTimeout, - }, - } -} - -// TestPersistEmptyApprovedRoutes covers the State.SetApprovedRoutes -// path. The gRPC handler builds the slice via append from a nil -// declaration, so when the operator passes `-r ""` the persist layer -// receives a nil []netip.Prefix. GORM's struct Updates skips nil -// slices, so the column would stay populated with the previously -// approved routes and a restart would re-apply them. -func TestPersistEmptyApprovedRoutes(t *testing.T) { - dbPath, s, nodeID := persistTestSetup(t) - - route := netip.MustParsePrefix("10.0.0.0/8") - - _, _, err := s.SetApprovedRoutes(nodeID, []netip.Prefix{route}) - require.NoError(t, err) - - gotAfterApprove, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - require.Equal(t, []netip.Prefix{route}, gotAfterApprove.ApprovedRoutes.List(), - "approved_routes should hold the seeded route") - - var noRoutes []netip.Prefix - - _, _, err = s.SetApprovedRoutes(nodeID, noRoutes) - require.NoError(t, err) - - gotAfterClear, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - assert.Empty(t, gotAfterClear.ApprovedRoutes, - "approved_routes should be empty after rejecting all routes, got %v", - gotAfterClear.ApprovedRoutes) - - require.NoError(t, s.Close()) - - s2 := persistTestReopen(t, dbPath) - - nv, ok := s2.GetNodeByID(nodeID) - require.True(t, ok, "node should be loaded from DB after restart") - assert.Empty(t, nv.AsStruct().ApprovedRoutes, - "after restart, NodeStore should reflect the cleared routes") -} - -// TestPersistEmptyTags exercises the same persist path for the tags -// column. State.SetNodeTags rejects an empty slice at the API level -// (tags are one-way), so the test drives the bug surface directly via -// NodeStore + persistNodeToDB, which is the same code path the public -// SetApprovedRoutes call exercises. -func TestPersistEmptyTags(t *testing.T) { - dbPath, s, nodeID := persistTestSetup(t) - - _, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Tags = []string{"tag:test"} - }) - require.True(t, ok) - - seeded, ok := s.nodeStore.GetNode(nodeID) - require.True(t, ok) - - _, _, err := s.persistNodeToDB(seeded) - require.NoError(t, err) - - gotAfterSeed, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - require.Equal(t, []string{"tag:test"}, gotAfterSeed.Tags.List(), - "tags should hold the seeded value") - - cleared, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Tags = nil - }) - require.True(t, ok) - - _, _, err = s.persistNodeToDB(cleared) - require.NoError(t, err) - - gotAfterClear, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - assert.Empty(t, gotAfterClear.Tags, - "tags should be empty after clear, got %v", gotAfterClear.Tags) - - require.NoError(t, s.Close()) - - s2 := persistTestReopen(t, dbPath) - - nv, ok := s2.GetNodeByID(nodeID) - require.True(t, ok) - assert.Empty(t, nv.AsStruct().Tags, - "after restart, NodeStore should reflect the cleared tags") -} - -// TestPersistEmptyEndpoints covers the endpoints column. Endpoints -// arrive via MapRequest in production; the test reaches the persist -// layer directly because the bug is in serialization, not in -// upstream parsing. -func TestPersistEmptyEndpoints(t *testing.T) { - dbPath, s, nodeID := persistTestSetup(t) - - endpoint := netip.MustParseAddrPort("198.51.100.1:41641") - - _, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Endpoints = []netip.AddrPort{endpoint} - }) - require.True(t, ok) - - seeded, ok := s.nodeStore.GetNode(nodeID) - require.True(t, ok) - - _, _, err := s.persistNodeToDB(seeded) - require.NoError(t, err) - - gotAfterSeed, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - require.Equal(t, []netip.AddrPort{endpoint}, gotAfterSeed.Endpoints.List(), - "endpoints should hold the seeded value") - - cleared, ok := s.nodeStore.UpdateNode(nodeID, func(n *types.Node) { - n.Endpoints = nil - }) - require.True(t, ok) - - _, _, err = s.persistNodeToDB(cleared) - require.NoError(t, err) - - gotAfterClear, err := s.DB().GetNodeByID(nodeID) - require.NoError(t, err) - assert.Empty(t, gotAfterClear.Endpoints, - "endpoints should be empty after clear, got %v", gotAfterClear.Endpoints) - - require.NoError(t, s.Close()) - - s2 := persistTestReopen(t, dbPath) - - nv, ok := s2.GetNodeByID(nodeID) - require.True(t, ok) - assert.Empty(t, nv.AsStruct().Endpoints, - "after restart, NodeStore should reflect the cleared endpoints") -} - -// TestRegistrationRejectsNodeKeyClaimedByAnotherMachine proves a new -// registration cannot claim a NodeKey already bound to a different machine. -// NodeKeys are public (peers learn them from the netmap), so without this -// check an authenticated party can register a node carrying a victim's -// NodeKey. That poisons the NodeStore NodeKey index (a map keyed on NodeKey, -// last writer wins), so the victim's MapRequest resolves to the attacker's -// node and is rejected by getAndValidateNode's MachineKey check (noise.go) — -// a denial of service against the victim. getAndValidateNode already enforces -// a 1:1 NodeKey<->MachineKey binding at poll time; this enforces the same -// invariant at registration time. -func TestRegistrationRejectsNodeKeyClaimedByAnotherMachine(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("nk-user") - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - sharedNodeKey := key.NewNode() - - _, err = s.createAndSaveNewNode(newNodeParams{ - User: *user, - MachineKey: key.NewMachine().Public(), - NodeKey: sharedNodeKey.Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "victim", - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - - // A different machine tries to register carrying the victim's NodeKey. - _, err = s.createAndSaveNewNode(newNodeParams{ - User: *user, - MachineKey: key.NewMachine().Public(), - NodeKey: sharedNodeKey.Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "attacker", - RegisterMethod: util.RegisterMethodCLI, - }) - require.Error(t, err, - "registering a NodeKey already bound to another machine must be rejected") -} - -// TestReauthRejectsNodeKeyClaimedByAnotherMachine proves the re-auth/update -// path enforces the same 1:1 NodeKey<->MachineKey binding as the create path -// (TestRegistrationRejectsNodeKeyClaimedByAnotherMachine) and the poll path -// (getAndValidateNode). Without it, a node re-authenticating could rotate its -// NodeKey to a victim's, poisoning the NodeStore NodeKey index so the victim's -// MapRequest resolves to the attacker's node and is rejected — a DoS. -func TestReauthRejectsNodeKeyClaimedByAnotherMachine(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - attacker := database.CreateUserForTest("attacker") - victim := database.CreateUserForTest("victim") - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - victimNodeKey := key.NewNode() - attackerMachine := key.NewMachine() - - // Victim's node holds victimNodeKey. - _, err = s.createAndSaveNewNode(newNodeParams{ - User: *victim, - MachineKey: key.NewMachine().Public(), - NodeKey: victimNodeKey.Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "victim", - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - - // Attacker registers its own node. - attackerNode, err := s.createAndSaveNewNode(newNodeParams{ - User: *attacker, - MachineKey: attackerMachine.Public(), - NodeKey: key.NewNode().Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "attacker", - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - - // Attacker re-authenticates its own node but supplies the victim's NodeKey. - _, err = s.applyAuthNodeUpdate(authNodeUpdateParams{ - ExistingNode: attackerNode, - RegData: &types.RegistrationData{ - MachineKey: attackerMachine.Public(), - NodeKey: victimNodeKey.Public(), - Hostname: "attacker", - Hostinfo: &tailcfg.Hostinfo{}, - }, - ValidHostinfo: &tailcfg.Hostinfo{}, - Hostname: "attacker", - User: attacker, - RegisterMethod: util.RegisterMethodCLI, - }) - require.Error(t, err, - "re-auth claiming a NodeKey bound to another machine must be rejected") -} - -// TestReauthPreservesEndpointsWhenClientOmitsThem proves the re-auth/update -// path keeps a node's live WireGuard endpoints when the originating -// RegisterRequest carried none. Web/OIDC relogins report endpoints via -// MapRequest, not register, so RegData.Endpoints is empty; wiping the stored -// endpoints would advertise the re-keyed node to peers endpoint-less, which -// drives head/unstable tailscale clients into one-way disco-deafness. -func TestReauthPreservesEndpointsWhenClientOmitsThem(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("user") - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - machine := key.NewMachine() - endpoints := []netip.AddrPort{ - netip.MustParseAddrPort("192.168.1.5:41641"), - netip.MustParseAddrPort("10.0.0.5:41641"), - } - - // Node is registered and has reported live endpoints (as after its first - // MapRequest). - node, err := s.createAndSaveNewNode(newNodeParams{ - User: *user, - MachineKey: machine.Public(), - NodeKey: key.NewNode().Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "node", - Endpoints: endpoints, - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - require.Equal(t, endpoints, node.Endpoints().AsSlice(), - "precondition: node has live endpoints") - - // Node re-authenticates, rotating its NodeKey. The RegisterRequest carries - // no endpoints. - updated, err := s.applyAuthNodeUpdate(authNodeUpdateParams{ - ExistingNode: node, - RegData: &types.RegistrationData{ - MachineKey: machine.Public(), - NodeKey: key.NewNode().Public(), - Hostname: "node", - Hostinfo: &tailcfg.Hostinfo{}, - Endpoints: nil, - }, - ValidHostinfo: &tailcfg.Hostinfo{}, - Hostname: "node", - User: user, - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - - assert.Equal(t, endpoints, updated.Endpoints().AsSlice(), - "re-auth without reported endpoints must preserve the node's live endpoints") -} - -// TestReauthChange covers the decision both re-auth paths share: a same-user -// relogin must be an incremental peer patch (so the tailscale client takes its -// fast patch path), never a whole-node add (which strands a re-keyed, -// momentarily-endpoint-less peer disco-deaf); a policy change forces a full -// recompute; a new node is a whole-node add. -func TestReauthChange(t *testing.T) { - n := types.Node{ - ID: 7, - NodeKey: key.NewNode().Public(), - DiscoKey: key.NewDisco().Public(), - } - node := n.View() - - relogin := reauthChange(node, true, false) - assert.Len(t, relogin.PeerPatches, 1, "relogin must be a peer patch") - assert.Empty(t, relogin.PeersChanged, "relogin must not be a whole-node add") - - added := reauthChange(node, false, false) - assert.Empty(t, added.PeerPatches) - assert.Len(t, added.PeersChanged, 1, "a new node must be a whole-node add") - - pol := reauthChange(node, true, true) - assert.Empty(t, pol.PeerPatches, "a policy change must not be a peer patch") - assert.Empty(t, pol.PeersChanged) - assert.False(t, pol.IsEmpty(), "a policy change must be non-empty") -} - -// TestPreAuthKeyReauthRejectsNodeKeyClaimedByAnotherMachine is the pre-auth-key -// analogue of TestReauthRejectsNodeKeyClaimedByAnotherMachine: re-registering -// via a pre-auth key must enforce the same 1:1 NodeKey<->MachineKey binding the -// auth path and poll-time validation enforce, so a node cannot rotate its key -// to a victim's and poison the NodeStore NodeKey index. -func TestPreAuthKeyReauthRejectsNodeKeyClaimedByAnotherMachine(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - attacker := s.CreateUserForTest("attacker") - victim := s.CreateUserForTest("victim") - - victimMachine := key.NewMachine() - victimNodeKey := key.NewNode() - _, err = s.createAndSaveNewNode(newNodeParams{ - User: *victim, - MachineKey: victimMachine.Public(), - NodeKey: victimNodeKey.Public(), - DiscoKey: key.NewDisco().Public(), - Hostname: "victim", - RegisterMethod: util.RegisterMethodCLI, - }) - require.NoError(t, err) - - // Attacker registers its own node with a reusable pre-auth key. - pak, err := s.CreatePreAuthKey(attacker.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - attackerMachine := key.NewMachine() - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "attacker"}, - Expiry: time.Now().Add(24 * time.Hour), - } - _, _, err = s.HandleNodeFromPreAuthKey(regReq, attackerMachine.Public()) - require.NoError(t, err) - - // Attacker re-registers its own node but supplies the victim's NodeKey. - attack := regReq - attack.NodeKey = victimNodeKey.Public() - _, _, err = s.HandleNodeFromPreAuthKey(attack, attackerMachine.Public()) - require.ErrorIs(t, err, ErrNodeKeyInUse, - "pre-auth-key re-registration claiming another machine's NodeKey must be rejected") - - // The victim still owns its NodeKey. - owner, ok := s.GetNodeByNodeKey(victimNodeKey.Public()) - require.True(t, ok) - require.Equal(t, victimMachine.Public(), owner.MachineKey(), - "victim's NodeKey index entry must be untouched") -} - -var errInjectedNodeUpdate = errors.New("injected node update failure") - -// TestPreAuthKeyReauthRevertsNodeStoreOnDBFailure ensures a failed database -// write during pre-auth-key re-registration does not leave the NodeStore -// holding a node key that was never persisted: a restart would reload the old -// row and the client's current key would no longer resolve, locking it out. -func TestPreAuthKeyReauthRevertsNodeStoreOnDBFailure(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("reauth-user") - - pak, err := s.CreatePreAuthKey(user.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "reauth-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - node, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - require.NoError(t, err) - - origNodeKey := node.NodeKey() - - // Fail the node row update so the re-registration's database write errors - // after the NodeStore has already been mutated. - require.NoError(t, s.db.DB.Callback().Update().Before("gorm:update"). - Register("fail_node_update", func(tx *gorm.DB) { - if tx.Statement.Table == "nodes" { - _ = tx.AddError(errInjectedNodeUpdate) - } - })) - - reReg := regReq - reReg.NodeKey = key.NewNode().Public() // rotate -> NodeStore mutation, then DB write fails - _, _, err = s.HandleNodeFromPreAuthKey(reReg, machineKey.Public()) - require.NoError(t, s.db.DB.Callback().Update().Remove("fail_node_update")) - require.Error(t, err, "re-registration must fail when the database write fails") - - got, ok := s.nodeStore.GetNode(node.ID()) - require.True(t, ok) - require.Equal(t, origNodeKey, got.NodeKey(), - "NodeStore must revert to the persisted node key when the write fails") -} - -// TestConcurrentPreAuthKeyRegistrationSameMachineKey ensures concurrent -// registrations of the same machine key resolve to a single node. Without -// serialising the find-then-create section, each request sees "no existing -// node" and creates its own, leaving duplicate nodes and IP allocations for -// one machine. -func TestConcurrentPreAuthKeyRegistrationSameMachineKey(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - user := s.CreateUserForTest("concurrent-user") - - pak, err := s.CreatePreAuthKey(user.TypedID(), true, false, nil, nil) - require.NoError(t, err) - - machineKey := key.NewMachine() - - const n = 12 - - var wg sync.WaitGroup - - start := make(chan struct{}) - errs := make(chan error, n) - - for range n { - wg.Go(func() { - regReq := tailcfg.RegisterRequest{ - Auth: &tailcfg.RegisterResponseAuth{AuthKey: pak.Key}, - NodeKey: key.NewNode().Public(), - Hostinfo: &tailcfg.Hostinfo{Hostname: "concurrent-node"}, - Expiry: time.Now().Add(24 * time.Hour), - } - - <-start - - _, _, err := s.HandleNodeFromPreAuthKey(regReq, machineKey.Public()) - errs <- err - }) - } - - close(start) - wg.Wait() - close(errs) - - for err := range errs { - require.NoError(t, err) - } - - require.Equal(t, 1, s.ListNodes().Len(), - "concurrent registrations of one machine key must yield a single node") -} diff --git a/hscontrol/state/ping.go b/hscontrol/state/ping.go index 2f688b946..43cf6914b 100644 --- a/hscontrol/state/ping.go +++ b/hscontrol/state/ping.go @@ -5,15 +5,15 @@ import ( "time" "github.com/juanfont/headscale/hscontrol/types" - "tailscale.com/util/rands" + "github.com/juanfont/headscale/hscontrol/util" ) const pingIDLength = 16 -// pingTracker correlates outgoing [tailcfg.PingRequest]s with incoming HEAD +// pingTracker correlates outgoing PingRequests with incoming HEAD // callbacks. Entries have no server-side TTL: callers are responsible -// for cleaning up via [pingTracker.cancel] or by reading from the response -// channel within their own timeout. +// for cleaning up via CancelPing or by reading from the response channel +// within their own timeout. type pingTracker struct { mu sync.Mutex pending map[string]*pendingPing @@ -35,7 +35,7 @@ func newPingTracker() *pingTracker { // channel that receives the round-trip latency once the response // arrives. func (pt *pingTracker) register(nodeID types.NodeID) (string, <-chan time.Duration) { - pingID := rands.HexString(pingIDLength) + pingID, _ := util.GenerateRandomStringDNSSafe(pingIDLength) ch := make(chan time.Duration, 1) pt.mu.Lock() @@ -80,7 +80,7 @@ func (pt *pingTracker) cancel(pingID string) { } // drain closes every outstanding response channel and clears the map. -// Called from [State.Close] to unblock any caller still waiting on a +// Called from State.Close to unblock any caller still waiting on a // channel that will never receive. func (pt *pingTracker) drain() { pt.mu.Lock() @@ -93,7 +93,7 @@ func (pt *pingTracker) drain() { } // RegisterPing tracks a pending ping and returns its ID and a channel -// for the latency. Callers must defer [State.CancelPing] or read the channel +// for the latency. Callers must defer CancelPing or read the channel // within their own timeout; there is no server-side TTL. func (s *State) RegisterPing(nodeID types.NodeID) (string, <-chan time.Duration) { return s.pings.register(nodeID) diff --git a/hscontrol/state/primaries_property_test.go b/hscontrol/state/primaries_property_test.go index 6507e2350..2c583f768 100644 --- a/hscontrol/state/primaries_property_test.go +++ b/hscontrol/state/primaries_property_test.go @@ -2,7 +2,6 @@ package state import ( "fmt" - "maps" "net/netip" "slices" "testing" @@ -24,19 +23,6 @@ import ( // the same answer from a separate, deliberately direct implementation. type primariesModel struct { connected map[types.NodeID]bool - // announced mirrors Hostinfo.RoutableIPs — what the client says - // it can route. The election does not look at this directly; it - // is used to recompute the effective set whenever ApprovedRoutes - // changes without a Hostinfo update. - announced map[types.NodeID][]netip.Prefix - // approved mirrors node.ApprovedRoutes — what the admin policy - // allows. SetApprovedRoutes touches this without touching - // announced; ConnectAdvertise / ApprovedRoutesChange touch both. - approved map[types.NodeID][]netip.Prefix - // prefixes is the effective per-node route set — - // AllApprovedRoutes in the implementation, i.e. (announced ∩ - // approved) excluding exit routes. This is what the election - // sees, and what the model uses in advertisersByPrefix. prefixes map[types.NodeID][]netip.Prefix unhealthy map[types.NodeID]bool @@ -50,42 +36,12 @@ type primariesModel struct { func newPrimariesModel() *primariesModel { return &primariesModel{ connected: map[types.NodeID]bool{}, - announced: map[types.NodeID][]netip.Prefix{}, - approved: map[types.NodeID][]netip.Prefix{}, prefixes: map[types.NodeID][]netip.Prefix{}, unhealthy: map[types.NodeID]bool{}, primary: map[netip.Prefix]types.NodeID{}, } } -// recomputeEffective sets m.prefixes[id] to the intersection of -// m.announced[id] and m.approved[id]. Empty intersections clear the -// node from m.prefixes entirely. Matches Node.AllApprovedRoutes / -// SubnetRoutes semantics (exit routes excluded — none of the test -// prefixes hit that branch). -func (m *primariesModel) recomputeEffective(id types.NodeID) { - ann := m.announced[id] - app := m.approved[id] - - if len(ann) == 0 || len(app) == 0 { - delete(m.prefixes, id) - return - } - - eff := make([]netip.Prefix, 0, len(ann)) - for _, p := range ann { - if slices.Contains(app, p) { - eff = append(eff, p) - } - } - - if len(eff) == 0 { - delete(m.prefixes, id) - } else { - m.prefixes[id] = eff - } -} - // advertisersByPrefix returns the connected nodes that announce each // prefix, sorted by NodeID (matches computePrimaries' iteration). func (m *primariesModel) advertisersByPrefix() map[netip.Prefix][]types.NodeID { @@ -141,21 +97,18 @@ func (m *primariesModel) updatePrimaries() { } } - // All-unhealthy fallback: preserve the previous primary if it - // is still a candidate, otherwise leave the prefix unmapped. - // Choosing any candidate would point peers at a node already - // declared unreachable; the model mirrors that policy. if !found && len(nodes) >= 1 { if cur, ok := m.primary[p]; ok && slices.Contains(nodes, cur) { selected = cur - found = true + } else { + selected = nodes[0] } + + found = true } if found { m.primary[p] = selected - } else { - delete(m.primary, p) } } } @@ -197,16 +150,8 @@ func samePrefixSet(a, b []netip.Prefix) bool { } // checkPrimariesProperties asserts every rule we expect of the snapshot's -// primaries map given the model. prevSnapshotPrimaries is the snapshot's -// PrimaryRoutes() reading taken before the just-applied op, used to -// catch flap regressions that move primary off a still-eligible owner. -func checkPrimariesProperties( - rt *rapid.T, - ns *NodeStore, - m *primariesModel, - nodeIDs []types.NodeID, - prevSnapshotPrimaries map[netip.Prefix]types.NodeID, -) { +// primaries map given the model. +func checkPrimariesProperties(rt *rapid.T, ns *NodeStore, m *primariesModel, nodeIDs []types.NodeID) { rt.Helper() expectedByNode := map[types.NodeID][]netip.Prefix{} @@ -234,9 +179,9 @@ func checkPrimariesProperties( } // Every prefix that has at least one connected advertiser must - // have a primary in the snapshot: a prefix silently losing its - // primary after a disconnect/reconnect cycle would leave peers - // without a route. + // have a primary in the snapshot. Issue #3203 manifests as a + // prefix silently losing its primary after a disconnect/reconnect + // cycle. for _, p := range m.allPrefixes() { want, expectExists := m.primary[p] if !expectExists { @@ -258,105 +203,6 @@ func checkPrimariesProperties( ) } } - - // Structural invariants on the live snapshot, independent of the - // model. These catch shapes the model alone cannot — e.g. an owner - // that is offline but still has the prefix attributed to it. - snapshotPrimaries := ns.PrimaryRoutes() - - // A primary that owns ≥1 prefix in `routes` must also light up - // `isPrimaryRoute` (PrimaryRoutesForNode returns nil unless the - // id is in that map). The inverse check — PrimaryRoutesForNode - // returning the right prefixes — is already covered above; this - // catches the reverse direction. - ownersInRoutes := map[types.NodeID]bool{} - for _, owner := range snapshotPrimaries { - ownersInRoutes[owner] = true - } - - for owner := range ownersInRoutes { - if got := ns.PrimaryRoutesForNode(owner); len(got) == 0 { - rt.Fatalf( - "node %d owns a prefix in routes but PrimaryRoutesForNode is empty", - owner, - ) - } - } - - // Per-owner structural checks: every primary must currently be - // online and must currently advertise the prefix it owns. - advertisersByPrefix := m.advertisersByPrefix() - - for prefix, owner := range snapshotPrimaries { - nv, ok := ns.GetNode(owner) - if !ok || !nv.Valid() { - rt.Fatalf( - "prefix %s primary %d not present in NodeStore", - prefix, owner, - ) - } - - // Primary is online: an offline node cannot move packets, so - // election must never leave one as the snapshot's primary. - online, known := nv.IsOnline().GetOk() - if !known || !online { - rt.Fatalf( - "prefix %s primary %d is not online", - prefix, owner, - ) - } - - // Primary advertises the prefix: AllApprovedRoutes (subnet ∪ - // exit) must contain it. A primary that no longer advertises - // is a stale assignment the snapshot rebuild failed to clear. - approved := nv.AllApprovedRoutes() - if !slices.Contains(approved, prefix) { - rt.Fatalf( - "prefix %s primary %d does not advertise it (approved=%v)", - prefix, owner, approved, - ) - } - - // Healthy preference: if any candidate for this prefix is - // healthy, the elected primary must also be healthy. Leaving - // the prefix unmapped is fine; pointing at an unhealthy - // candidate while a healthy one was available is not. - candidates := advertisersByPrefix[prefix] - anyHealthy := false - - for _, c := range candidates { - if !m.unhealthy[c] { - anyHealthy = true - break - } - } - - if anyHealthy && m.unhealthy[owner] { - rt.Fatalf( - "prefix %s primary %d is unhealthy but %v had a healthy candidate", - prefix, owner, candidates, - ) - } - - // Anti-flap: if the previous snapshot already had a primary - // for this prefix AND that primary is still a healthy - // candidate after the op, the election must keep it. Flapping - // the primary for an unrelated reason violates the contract - // the integration tests rely on (HA failover only moves on - // loss of candidacy or health). - if prev, hadPrev := prevSnapshotPrimaries[prefix]; hadPrev { - stillCandidate := slices.Contains(candidates, prev) - stillHealthy := !m.unhealthy[prev] - - if stillCandidate && stillHealthy && owner != prev { - rt.Fatalf( - "prefix %s flapped primary %d -> %d "+ - "while previous primary was still a healthy candidate", - prefix, prev, owner, - ) - } - } - } } // nodeForRapid builds a minimal types.Node for use in property @@ -378,41 +224,16 @@ func nodeForRapid(id types.NodeID) types.Node { } } -// snapshotPrimariesCopy returns a defensive copy of the snapshot's -// prefix→primary map so the caller can compare against a later -// snapshot without aliasing the live map. -func snapshotPrimariesCopy(ns *NodeStore) map[netip.Prefix]types.NodeID { - live := ns.PrimaryRoutes() - - out := make(map[netip.Prefix]types.NodeID, len(live)) - maps.Copy(out, live) - - return out -} - // TestPrimaryRoutesProperty drives NodeStore with a randomised // sequence of high-level operations and checks that the snapshot's // primaries map matches a reference model after every step. // -// Op set (covers the dual-disconnect, batched-probe, and -// all-unhealthy-fallback shapes that election has to handle): -// -// - ConnectAdvertise: online + advertise prefs, clear Unhealthy -// - Disconnect: offline; ApprovedRoutes persists -// - ProbeUnhealthy: set Unhealthy -// - ProbeHealthy: clear Unhealthy -// - ApprovedRoutesChange: change advertised prefs without touching health -// - BatchProbeResults: apply a batch of health flips through -// UpdateNodes so the election runs once for the cycle, matching -// State.BatchSetNodeHealth -// - SimultaneousDisconnect: mark multiple nodes offline atomically -// via UpdateNodes (the dual-cable-pull shape) -// - SetApprovedRoutes: change ApprovedRoutes while leaving -// Hostinfo.RoutableIPs alone — exercises the asymmetry between -// announced and approved -// - OfflineExpiry: IsOnline=false WITHOUT clearing Unhealthy -// (matches the Disconnect path's actual semantics versus -// ConnectAdvertise's full reset) +// Background: issue #3203 reports that HA tracking enters a stuck +// state after a sequence of disconnect/reconnect events. The narrow +// integration and servertest reproductions written for the bug do +// not fail on upstream/main, so this property test broadens the +// search by letting rapid generate sequences we have not enumerated +// by hand. func TestPrimaryRoutesProperty(t *testing.T) { rapid.Check(t, func(rt *rapid.T) { const numNodes = 4 @@ -444,29 +265,14 @@ func TestPrimaryRoutesProperty(t *testing.T) { 0, len(prefixes), func(p netip.Prefix) string { return p.String() }, ) - // nodeSubsetGen draws 1..N distinct node IDs for the batched - // ops. SliceOfNDistinct's lower bound is inclusive, so 1 is - // the smallest sensible batch — a zero-size batch is a no-op - // the rapid harness should not waste shrinking cycles on. - nodeSubsetGen := rapid.SliceOfNDistinct( - nodeGen, - 1, numNodes, - func(id types.NodeID) types.NodeID { return id }, - ) - opCount := rapid.IntRange(5, 200).Draw(rt, "opCount") + opCount := rapid.IntRange(5, 60).Draw(rt, "opCount") for step := range opCount { - op := rapid.IntRange(0, 8).Draw(rt, fmt.Sprintf("op_%d", step)) - - // Snapshot primaries before applying the op so the - // anti-flap invariant has a stable reference. Reading - // after the model has already changed would compare a - // stale snapshot to a moved model. - prevPrimaries := snapshotPrimariesCopy(ns) + op := rapid.IntRange(0, 4).Draw(rt, fmt.Sprintf("op_%d", step)) + id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) switch op { case 0: // ConnectAdvertise — Connect path clears Unhealthy. - id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) prefs := prefixSubsetGen.Draw(rt, fmt.Sprintf("prefs_%d", step)) ns.UpdateNode(id, func(n *types.Node) { @@ -476,41 +282,35 @@ func TestPrimaryRoutesProperty(t *testing.T) { n.ApprovedRoutes = prefs }) - m.announced[id] = prefs - m.approved[id] = prefs - m.recomputeEffective(id) - if len(prefs) == 0 { delete(m.connected, id) + delete(m.prefixes, id) } else { m.connected[id] = true + m.prefixes[id] = prefs } delete(m.unhealthy, id) case 1: // Disconnect — IsOnline=false; ApprovedRoutes persists. - id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) ns.UpdateNode(id, func(n *types.Node) { n.IsOnline = new(false) }) delete(m.connected, id) case 2: // ProbeUnhealthy — HA prober marks node bad. - id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) ns.UpdateNode(id, func(n *types.Node) { n.Unhealthy = true }) m.unhealthy[id] = true case 3: // ProbeHealthy — HA prober marks node good. - id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) ns.UpdateNode(id, func(n *types.Node) { n.Unhealthy = false }) delete(m.unhealthy, id) case 4: // ApprovedRoutesChange — change advertised prefs without touching health. - id := nodeGen.Draw(rt, fmt.Sprintf("id_%d", step)) prefs := prefixSubsetGen.Draw(rt, fmt.Sprintf("prefs_%d", step)) ns.UpdateNode(id, func(n *types.Node) { @@ -519,107 +319,18 @@ func TestPrimaryRoutesProperty(t *testing.T) { n.ApprovedRoutes = prefs }) - m.announced[id] = prefs - m.approved[id] = prefs - m.recomputeEffective(id) - if len(prefs) == 0 { delete(m.connected, id) + delete(m.prefixes, id) } else { m.connected[id] = true + m.prefixes[id] = prefs } - - case 5: // BatchProbeResults — atomic health flips per cycle. - // Mirrors State.BatchSetNodeHealth: per-node - // (id, unhealthy) pairs applied through UpdateNodes - // so the election runs once on the post-batch state. - // Per-call publication would let an intermediate - // "one unhealthy, one healthy" snapshot re-elect off - // the still-healthy node before the second flip - // landed. - ids := nodeSubsetGen.Draw(rt, fmt.Sprintf("batch_ids_%d", step)) - - results := make(map[types.NodeID]bool, len(ids)) - for i, id := range ids { - unhealthy := rapid.Bool().Draw(rt, fmt.Sprintf("batch_h_%d_%d", step, i)) - results[id] = unhealthy - } - - fns := make(map[types.NodeID]UpdateNodeFunc, len(results)) - for id, unhealthy := range results { - fns[id] = func(n *types.Node) { - n.Unhealthy = unhealthy - } - } - - ns.UpdateNodes(fns) - - for id, unhealthy := range results { - if unhealthy { - m.unhealthy[id] = true - } else { - delete(m.unhealthy, id) - } - } - - case 6: // SimultaneousDisconnect — multiple offline in one batch. - // Dual-cable-pull shape: two HA routers' poll - // sessions both close in the same NodeStore tick. - // Per-call Disconnect could leave the snapshot - // momentarily pointing at an offline owner; the - // batched form forces a single rebuild. - ids := nodeSubsetGen.Draw(rt, fmt.Sprintf("disc_ids_%d", step)) - - fns := make(map[types.NodeID]UpdateNodeFunc, len(ids)) - for _, id := range ids { - fns[id] = func(n *types.Node) { - n.IsOnline = new(false) - } - } - - ns.UpdateNodes(fns) - - for _, id := range ids { - delete(m.connected, id) - } - - case 7: // SetApprovedRoutes — change ApprovedRoutes only. - // SetApprovedRoutes in production updates only - // node.ApprovedRoutes; Hostinfo.RoutableIPs (what - // the client announced) is set by the next - // MapRequest. SubnetRoutes intersects the two, so - // dropping ApprovedRoutes mid-flight shrinks the - // advertised set immediately while announcement - // alone never extends it. - id := nodeGen.Draw(rt, fmt.Sprintf("setapp_id_%d", step)) - prefs := prefixSubsetGen.Draw(rt, fmt.Sprintf("setapp_prefs_%d", step)) - - ns.UpdateNode(id, func(n *types.Node) { - n.ApprovedRoutes = prefs - }) - - m.approved[id] = prefs - m.recomputeEffective(id) - - case 8: // OfflineExpiry — IsOnline=false, KEEP Unhealthy. - // The Disconnect path does not clear Unhealthy - // (only ConnectAdvertise does on the way back - // up). A probe that marks unhealthy and a grace- - // period disconnect that lands later leaves the - // node with a stale Unhealthy bit; the test - // exercises that shape. - id := nodeGen.Draw(rt, fmt.Sprintf("expire_id_%d", step)) - ns.UpdateNode(id, func(n *types.Node) { - n.IsOnline = new(false) - }) - - delete(m.connected, id) - // m.unhealthy[id] is intentionally NOT cleared. } m.updatePrimaries() - checkPrimariesProperties(rt, ns, m, nodeIDs, prevPrimaries) + checkPrimariesProperties(rt, ns, m, nodeIDs) } }) } diff --git a/hscontrol/state/primaries_test.go b/hscontrol/state/primaries_test.go index 1ad13d156..ab6116a3e 100644 --- a/hscontrol/state/primaries_test.go +++ b/hscontrol/state/primaries_test.go @@ -72,7 +72,7 @@ func (f *primariesFixture) disconnect(id types.NodeID) { }) } -// unhealthy mirrors State.SetNodeHealth(id, false). +// unhealthy mirrors State.SetNodeUnhealthy(id, true). func (f *primariesFixture) unhealthy(id types.NodeID) { f.t.Helper() f.ns.UpdateNode(id, func(n *types.Node) { @@ -80,7 +80,7 @@ func (f *primariesFixture) unhealthy(id types.NodeID) { }) } -// healthy mirrors State.SetNodeHealth(id, true). +// healthy mirrors State.SetNodeUnhealthy(id, false). func (f *primariesFixture) healthy(id types.NodeID) { f.t.Helper() f.ns.UpdateNode(id, func(n *types.Node) { @@ -218,12 +218,12 @@ func TestPrimaries_AllUnhealthyKeepsAPrimary(t *testing.T) { } func TestPrimaries_AllUnhealthyPreservesPrevious(t *testing.T) { - // Once a failover has moved primary to a higher-ID node, a - // subsequent all-unhealthy state must NOT churn primary back to - // the lowest-ID candidate. Under cable-pull semantics both nodes - // can linger as IsOnline=true (half-open TCP) and both go - // Unhealthy — naive `candidates[0]` would flap the primary to a - // node that is itself unreachable. + // Issue #3203: once a failover has moved primary to a higher-ID + // node, a subsequent all-unhealthy state must NOT churn primary + // back to the lowest-ID candidate. Under cable-pull semantics + // both nodes can linger as IsOnline=true (half-open TCP) and + // both go Unhealthy — naive `candidates[0]` would flap the + // primary to a node that is itself unreachable. prefix := mp("10.0.0.0/24") f := newPrimariesFixture(t, 1, 2) f.advertise(1, prefix) @@ -247,11 +247,12 @@ func TestPrimaries_ExitRouteNotElected(t *testing.T) { f.requireNoPrimary(exitV4) } -func TestPrimaries_BothOfflineThenOneReturns(t *testing.T) { - // With two HA advertisers, dropping both then bringing one back - // used to leave the prefix without any primary. The snapshot - // recomputes primaries on every NodeStore write, so the - // returning advertiser must be elected. +func TestPrimaries_RegressionIssue3203_BothOfflineThenOneReturns(t *testing.T) { + // Issue #3203: with two HA advertisers, dropping both then + // bringing one back used to leave the prefix without any + // primary. After the refactor the snapshot recomputes primaries + // on every NodeStore write, so the returning advertiser must + // be elected. prefix := mp("10.0.0.0/24") f := newPrimariesFixture(t, 1, 2) f.advertise(1, prefix) diff --git a/hscontrol/state/rename_test.go b/hscontrol/state/rename_test.go deleted file mode 100644 index b141147be..000000000 --- a/hscontrol/state/rename_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package state - -import ( - "strings" - "testing" - - "github.com/juanfont/headscale/hscontrol/db" - "github.com/stretchr/testify/require" -) - -// TestRenameNodeRejectsNameExceedingFQDNLimit proves RenameNode rejects a name -// that is a valid DNS label but whose FQDN, under the configured base_domain, -// exceeds MaxHostnameLength. Without the FQDN-length gate such a name persists -// and then breaks map generation for the node and its peers (issue #3346): -// admin-facing writes must not be able to introduce an unmappable name. -func TestRenameNodeRejectsNameExceedingFQDNLimit(t *testing.T) { - dbPath := t.TempDir() + "/headscale.db" - cfg := persistTestConfig(dbPath) - // A long base domain so a 63-char label overflows the 255-char FQDN bound. - cfg.BaseDomain = strings.Repeat("b", 200) + ".example.com" - - database, err := db.NewHeadscaleDatabase(cfg) - require.NoError(t, err) - - user := database.CreateUserForTest("rename-user") - node := database.CreateRegisteredNodeForTest(user, "rename-node") - require.NoError(t, database.Close()) - - s, err := NewState(cfg) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - // Valid 63-char DNS label, but the resulting FQDN exceeds 255 chars. - _, _, err = s.RenameNode(node.ID, strings.Repeat("a", 63)) - require.Error(t, err, "rename to a name whose FQDN exceeds the limit must be rejected") - - // A short, valid name is still accepted. - _, _, err = s.RenameNode(node.ID, "short") - require.NoError(t, err) -} diff --git a/hscontrol/state/state.go b/hscontrol/state/state.go index ddb410eb7..5ff95e2cf 100644 --- a/hscontrol/state/state.go +++ b/hscontrol/state/state.go @@ -2,11 +2,11 @@ // coordinating between subsystems like database, IP allocation, // policy management, and DERP routing. // -// The central type [State] owns a copy-on-write [NodeStore] -// (node_store.go), a PrimaryRoutes HA ledger, the [policy.PolicyManager], -// and a [pingTracker] for [tailcfg.PingRequest] correlation. -// Cross-subsystem operations (node updates, policy evaluation, IP -// allocation) go through [State] rather than directly to the database. +// The central type State owns a copy-on-write NodeStore +// (node_store.go), a PrimaryRoutes HA ledger, the PolicyManager, and a +// pingTracker for PingRequest correlation. Cross-subsystem operations +// (node updates, policy evaluation, IP allocation) go through State +// rather than directly to the database. package state @@ -32,9 +32,9 @@ import ( "github.com/juanfont/headscale/hscontrol/types/change" "github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util/zlog/zf" - "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "golang.org/x/sync/errgroup" "gorm.io/gorm" "tailscale.com/tailcfg" "tailscale.com/types/key" @@ -71,7 +71,7 @@ var ErrNodeNotFound = errors.New("node not found") // ErrInvalidNodeView is returned when an invalid node view is provided. var ErrInvalidNodeView = errors.New("invalid node view provided") -// ErrNodeNotInNodeStore is returned when a node no longer exists in the [NodeStore]. +// ErrNodeNotInNodeStore is returned when a node no longer exists in the NodeStore. var ErrNodeNotInNodeStore = errors.New("node no longer exists in NodeStore") // ErrNodeNameNotUnique is returned when a node name is not unique. @@ -113,18 +113,6 @@ var nodeUpdateColumns = []string{ // ErrRegistrationExpired is returned when a registration has expired. var ErrRegistrationExpired = errors.New("registration expired") -// ErrNodeKeyInUse is returned when a registration or re-auth claims a NodeKey -// already bound to a different machine, enforcing the 1:1 NodeKey<->MachineKey -// binding. -var ErrNodeKeyInUse = errors.New("node key already in use by another machine") - -// ErrAmbiguousNodeOwnership is returned when a machine key maps to a set of -// nodes from which the correct one to update or convert cannot be determined: -// multiple user-owned candidates for a tagged conversion, or a tagged node and -// a user-owned node coexisting (impossible per validateNodeOwnership). The -// registration is rejected rather than mutating an arbitrarily-picked node. -var ErrAmbiguousNodeOwnership = errors.New("machine key maps to ambiguous node ownership") - // sshCheckPair identifies a (source, destination) node pair for // SSH check auth tracking. type sshCheckPair struct { @@ -134,9 +122,6 @@ type sshCheckPair struct { // State manages Headscale's core state, coordinating between database, policy management, // IP allocation, and DERP routing. All methods are thread-safe. -// -// See [policy.PolicyManager] for policy evaluation and [NodeStore] for the -// in-memory node cache. type State struct { // cfg holds the current Headscale configuration cfg *types.Config @@ -182,33 +167,15 @@ type State struct { // Ref: https://github.com/tailscale/tailscale/issues/7125 sshCheckAuth map[sshCheckPair]time.Time sshCheckMu sync.RWMutex - - // persistMu serialises the re-read-and-write critical section in - // persistNodeToDB so the database row always converges on [NodeStore] - // rather than being clobbered by a stale caller snapshot. - persistMu sync.Mutex - - // registerLocks serialises registration per machine key so concurrent - // registrations of the same machine resolve to a single node instead of - // racing the find-then-create section and each creating their own. - // ponytail: entries are never pruned; bounded by distinct machine keys - // seen, add cleanup on node delete only if it ever matters. - registerLocks *xsync.Map[key.MachinePublic, *sync.Mutex] } -// lockRegistration serialises registration for a single machine key and -// returns the unlock function. -func (s *State) lockRegistration(machineKey key.MachinePublic) func() { - mu, _ := s.registerLocks.LoadOrStore(machineKey, &sync.Mutex{}) - mu.Lock() - - return mu.Unlock -} - -// NewState creates and initializes a new [State] instance, setting up the database, +// NewState creates and initializes a new State instance, setting up the database, // IP allocator, DERP map, policy manager, and loading existing users and nodes. func NewState(cfg *types.Config) (*State, error) { - cacheExpiration := cmp.Or(cfg.Tuning.RegisterCacheExpiration, registerCacheExpiration) + cacheExpiration := registerCacheExpiration + if cfg.Tuning.RegisterCacheExpiration != 0 { + cacheExpiration = cfg.Tuning.RegisterCacheExpiration + } cacheMaxEntries := defaultRegisterCacheMaxEntries if cfg.Tuning.RegisterCacheMaxEntries > 0 { @@ -244,7 +211,7 @@ func NewState(cfg *types.Config) (*State, error) { node.IsOnline = new(false) } - users, err := db.ListUsers(nil) + users, err := db.ListUsers() if err != nil { return nil, fmt.Errorf("loading users: %w", err) } @@ -259,13 +226,19 @@ func NewState(cfg *types.Config) (*State, error) { return nil, fmt.Errorf("initializing policy manager: %w", err) } - // Apply defaults for [NodeStore] batch configuration if not set. + // Apply defaults for NodeStore batch configuration if not set. // This ensures tests that create Config directly (without viper) still work. - batchSize := cmp.Or(cfg.Tuning.NodeStoreBatchSize, defaultNodeStoreBatchSize) + batchSize := cfg.Tuning.NodeStoreBatchSize + if batchSize == 0 { + batchSize = defaultNodeStoreBatchSize + } - batchTimeout := cmp.Or(cfg.Tuning.NodeStoreBatchTimeout, defaultNodeStoreBatchTimeout) + batchTimeout := cfg.Tuning.NodeStoreBatchTimeout + if batchTimeout == 0 { + batchTimeout = defaultNodeStoreBatchTimeout + } - // [policy.PolicyManager.BuildPeerMap] handles both global and per-node filter complexity. + // PolicyManager.BuildPeerMap handles both global and per-node filter complexity. // This moves the complex peer relationship logic into the policy package where it belongs. nodeStore := NewNodeStore( nodes, @@ -277,7 +250,7 @@ func NewState(cfg *types.Config) (*State, error) { ) nodeStore.Start() - s := &State{ + return &State{ cfg: cfg, db: db, @@ -287,19 +260,11 @@ func NewState(cfg *types.Config) (*State, error) { nodeStore: nodeStore, pings: newPingTracker(), - sshCheckAuth: make(map[sshCheckPair]time.Time), - registerLocks: xsync.NewMap[key.MachinePublic, *sync.Mutex](), - } - - // Surface nodes whose stored data would break map generation (e.g. an - // invalid given name from a legacy row) so an operator can fix them. This - // only logs; it never mutates a node's stored name at boot. - s.logNodeHealth() - - return s, nil + sshCheckAuth: make(map[sshCheckPair]time.Time), + }, nil } -// Close gracefully shuts down the [State] instance and releases all resources. +// Close gracefully shuts down the State instance and releases all resources. func (s *State) Close() error { s.pings.drain() s.nodeStore.Stop() @@ -323,7 +288,7 @@ func (s *State) DERPMap() tailcfg.DERPMapView { } // ReloadPolicy reloads the access control policy and triggers auto-approval if changed. -// Returns the resulting [change.Change] slice when the policy or routes changed. +// Returns true if the policy changed. func (s *State) ReloadPolicy() ([]change.Change, error) { pol, err := hsdb.PolicyBytes(s.db.DB, s.cfg) if err != nil { @@ -339,18 +304,18 @@ func (s *State) ReloadPolicy() ([]change.Change, error) { // approvals don't persist if checkPeriod rules are modified or removed. s.ClearSSHCheckAuth() - // Rebuild peer maps after policy changes because the peersFunc in [NodeStore] - // uses the [policy.PolicyManager]'s filters. Without this, nodes won't see - // newly allowed peers until a node is added/removed, causing autogroup:self - // policies to not propagate correctly when switching between policy types. + // Rebuild peer maps after policy changes because the peersFunc in NodeStore + // uses the PolicyManager's filters. Without this, nodes won't see newly allowed + // peers until a node is added/removed, causing autogroup:self policies to not + // propagate correctly when switching between policy types. s.nodeStore.RebuildPeerMaps() //nolint:prealloc // cs starts with one element and may grow cs := []change.Change{change.PolicyChange()} // Per-node selective self refresh for nodeAttrs. A broadcast - // [change.PolicyChange] re-renders peer lists and packet filters - // but never repopulates a node's own [tailcfg.Node.CapMap]; that + // PolicyChange() re-renders peer lists and packet filters but + // never repopulates a node's own [tailcfg.Node.CapMap]; that // lives on the self entry only. The drain returns every node ID // whose cap output shifted across recent updateLocked calls — // refreshNodeAttrsLocked appends rather than overwrites so a @@ -506,88 +471,64 @@ func (s *State) ListUsersWithFilter(filter *types.User) ([]types.User, error) { // ListAllUsers retrieves all users in the system. func (s *State) ListAllUsers() ([]types.User, error) { - return s.db.ListUsers(nil) + return s.db.ListUsers() } -// persistNodeRowToDB writes the node's database row, re-reading the -// authoritative copy from [NodeStore], without touching the policy manager. -// Batch callers (e.g. autoApproveNodes) use it to write many rows and then -// trigger a single policy rebuild instead of one per node. -func (s *State) persistNodeRowToDB(node types.NodeView) (types.NodeView, error) { +// persistNodeToDB saves the given node state to the database. +// This function must receive the exact node state to save to ensure consistency between +// NodeStore and the database. It verifies the node still exists in NodeStore to prevent +// race conditions where a node might be deleted between UpdateNode returning and +// persistNodeToDB being called. +func (s *State) persistNodeToDB(node types.NodeView) (types.NodeView, change.Change, error) { if !node.Valid() { - return types.NodeView{}, ErrInvalidNodeView + return types.NodeView{}, change.Change{}, ErrInvalidNodeView } - // [NodeStore] is the source of truth and every caller updates it before - // persisting. Re-read the authoritative node under persistMu and write - // that, rather than the caller's `node` view which may have been captured - // earlier (e.g. at the top of UpdateNodeFromMapRequest) and gone stale - // behind a concurrent admin write such as SetNodeTags. Serialising the - // read+write keeps the database row converging on [NodeStore] instead of - // reverting it to an out-of-date column set. - // - // The same re-read also guards against the node having been deleted (e.g. - // ephemeral logout) between the caller's update and this persist: a missing - // node means we must not re-insert it. - s.persistMu.Lock() - - fresh, exists := s.nodeStore.GetNode(node.ID()) + // Verify the node still exists in NodeStore before persisting to database. + // Without this check, we could hit a race condition where UpdateNode returns a valid + // node from a batch update, then the node gets deleted (e.g., ephemeral node logout), + // and persistNodeToDB would incorrectly re-insert the deleted node into the database. + _, exists := s.nodeStore.GetNode(node.ID()) if !exists { - s.persistMu.Unlock() - log.Warn(). EmbedObject(node). Bool("is_ephemeral", node.IsEphemeral()). Msg("Node no longer exists in NodeStore, skipping database persist to prevent race condition") - return types.NodeView{}, fmt.Errorf("%w: %d", ErrNodeNotInNodeStore, node.ID()) + return types.NodeView{}, change.Change{}, fmt.Errorf("%w: %d", ErrNodeNotInNodeStore, node.ID()) } - nodePtr := fresh.AsStruct() + nodePtr := node.AsStruct() // Explicitly select all node columns so GORM includes nil/zero-value // fields (e.g. UserID=nil when converting a user-owned node to tagged). // Omit "Expiry" here: expiry is only updated through explicit // SetNodeExpiry calls or re-registration, not during MapRequest updates. err := s.db.DB.Select(nodeUpdateColumns).Omit("Expiry").Updates(nodePtr).Error - s.persistMu.Unlock() - if err != nil { - return types.NodeView{}, fmt.Errorf("saving node: %w", err) - } - - return fresh, nil -} - -// persistNodeToDB saves the given node state to the database and refreshes the -// policy manager. The exact row written comes from [NodeStore]; see -// [State.persistNodeRowToDB]. -func (s *State) persistNodeToDB(node types.NodeView) (types.NodeView, change.Change, error) { - fresh, err := s.persistNodeRowToDB(node) - if err != nil { - return types.NodeView{}, change.Change{}, err + return types.NodeView{}, change.Change{}, fmt.Errorf("saving node: %w", err) } // Check if policy manager needs updating c, err := s.updatePolicyManagerNodes() if err != nil { - return fresh, change.Change{}, fmt.Errorf("updating policy manager after node save: %w", err) + return nodePtr.View(), change.Change{}, fmt.Errorf("updating policy manager after node save: %w", err) } if c.IsEmpty() { c = change.NodeAdded(node.ID()) } - return fresh, c, nil + return node, c, nil } func (s *State) SaveNode(node types.NodeView) (types.NodeView, change.Change, error) { - // Update [NodeStore] first + // Update NodeStore first nodePtr := node.AsStruct() resultNode := s.nodeStore.PutNode(*nodePtr) - // Then save to database using the result from [NodeStore.PutNode] + // Then save to database using the result from PutNode return s.persistNodeToDB(resultNode) } @@ -621,20 +562,18 @@ func (s *State) DeleteNode(node types.NodeView) (change.Change, error) { } // Connect marks a node connected and returns the resulting changes -// plus a session epoch identifying this poll session. Every Connect -// acquires one live session; the caller must release it with exactly -// one [State.Disconnect] call once the session ends (see poll.go). +// plus a session epoch. The caller must pass the epoch back to +// Disconnect so deferred grace-period disconnects from a previous +// poll session are dropped (see poll.go). func (s *State) Connect(id types.NodeID) ([]change.Change, uint64) { prevRoutes := s.nodeStore.PrimaryRoutes() // Reconnecting clears Unhealthy: the node just proved basic // connectivity by completing the Noise handshake. var epoch uint64 - node, ok := s.nodeStore.UpdateNode(id, func(n *types.Node) { n.SessionEpoch++ epoch = n.SessionEpoch - n.ActiveSessions++ n.IsOnline = new(true) n.Unhealthy = false }) @@ -642,10 +581,7 @@ func (s *State) Connect(id types.NodeID) ([]change.Change, uint64) { return nil, 0 } - // A node coming online sends a lightweight online peer patch. Subnet - // routers, relay targets, and via targets get their full peer recompute - // from the gated PolicyChange below, so no full update is needed here. - c := []change.Change{change.NodeOnline(node.ID())} + c := []change.Change{change.NodeOnlineFor(node)} log.Info().EmbedObject(node).Msg("node connected") @@ -653,44 +589,25 @@ func (s *State) Connect(id types.NodeID) ([]change.Change, uint64) { c = append(c, change.NodeAdded(id)) } - // Only a node whose online state changes what peers compute (a subnet - // router, relay target, or via target) needs a full peer recompute. - // An ordinary node coming online just sends the lightweight online - // patch above; emitting a PolicyChange for it would force every peer - // to rebuild its netmap on every reconnect. - if s.polMan.NodeNeedsPeerRecompute(node) { - c = append(c, change.PolicyChange()) - } + // Coming online may re-enable cap/relay grants and identity-based + // aliases targeting this node, so peers need a fresh netmap. + c = append(c, change.PolicyChange()) return c, epoch } -// Disconnect releases one poll session previously acquired by -// [State.Connect] and marks the node offline only when that was its -// last live session. Sessions are counted rather than compared by -// epoch: overlapping sessions for one node — a rapid reconnect, or a -// cancelled map request whose handler ran late — release in any order -// without stranding the node. An epoch-equality gate here loses when a -// dead-on-arrival session's Connect steals the latest epoch and its -// cleanup skips the release: the surviving session's Disconnect was -// then rejected as stale and the node stayed online forever. -// The count check and the IsOnline write share a -// [NodeStore.UpdateNode] closure, making them atomic against -// concurrent connects. epoch identifies the session for logging only. +// Disconnect marks the node offline. epoch must match the value +// Connect returned for this session; otherwise the call no-ops so a +// deferred disconnect from an older session cannot overwrite state set +// by a newer Connect. The check and the IsOnline write share an +// UpdateNode closure, making them atomic against concurrent Connects. func (s *State) Disconnect(id types.NodeID, epoch uint64) ([]change.Change, error) { - var wentOffline bool - + var stale bool node, ok := s.nodeStore.UpdateNode(id, func(n *types.Node) { - if n.ActiveSessions > 0 { - n.ActiveSessions-- - } - - if n.ActiveSessions > 0 { + if n.SessionEpoch != epoch { + stale = true return } - - wentOffline = true - now := time.Now() n.LastSeen = &now n.IsOnline = new(false) @@ -703,18 +620,18 @@ func (s *State) Disconnect(id types.NodeID, epoch uint64) ([]change.Change, erro return nil, fmt.Errorf("%w: %d", ErrNodeNotFound, id) } - if !wentOffline { + if stale { log.Debug(). Uint64("disconnect_epoch", epoch). - Int("active_sessions", node.ActiveSessions()). - Msg("session released, other sessions keep node online") + Uint64("current_epoch", node.SessionEpoch()). + Msg("stale disconnect rejected, newer session active") return nil, nil } log.Info().EmbedObject(node).Msg("node disconnected") - // Persist LastSeen best-effort: [NodeStore] already reflects offline + // Persist LastSeen best-effort: NodeStore already reflects offline // and peers still need the change notifications below. _, c, err := s.persistNodeToDB(node) if err != nil { @@ -723,18 +640,13 @@ func (s *State) Disconnect(id types.NodeID, epoch uint64) ([]change.Change, erro c = change.Change{} } - // Only a node whose online state changes what peers compute (a subnet - // router, relay target, or via target) needs a full peer recompute. - // An ordinary node going offline just sends the lightweight offline - // patch; emitting a PolicyChange for it would force every peer to - // rebuild its netmap on every disconnect. - // A node going offline sends a lightweight offline peer patch. Subnet - // routers and other recompute-forcing nodes rely on the gated - // PolicyChange below for the peer recompute, so no full update here. - cs := []change.Change{change.NodeOffline(node.ID()), c} - if s.polMan.NodeNeedsPeerRecompute(node) { - cs = append(cs, change.PolicyChange()) - } + // Going offline can affect policy compilation beyond subnet routes + // (cap/relay grants, tag/group aliases, via routes), so peers need + // a fresh netmap regardless of whether the primary moved. + // + // TODO(kradalby): fires one full netmap recompute per peer on + // every connect/disconnect. Coalesce in mapper/batcher.go:addToBatch. + cs := []change.Change{change.NodeOfflineFor(node), c, change.PolicyChange()} return cs, nil } @@ -756,11 +668,12 @@ func (s *State) GetNodeByNodeKey(nodeKey key.NodePublic) (types.NodeView, bool) return s.nodeStore.GetNodeByNodeKey(nodeKey) } -// GetNodesByMachineKeyAllUsers returns every node sharing the machine key, -// keyed by owning UserID (tagged nodes under UserID(0)). See -// [NodeStore.GetNodesByMachineKeyAllUsers]. -func (s *State) GetNodesByMachineKeyAllUsers(machineKey key.MachinePublic) map[types.UserID]types.NodeView { - return s.nodeStore.GetNodesByMachineKeyAllUsers(machineKey) +// GetNodeByMachineKey retrieves a node by its machine key and user ID. +// The bool indicates if the node exists or is available (like "err not found"). +// The NodeView might be invalid, so it must be checked with .Valid(), which must be used to ensure +// it isn't an invalid node (this is more of a node error or node is broken). +func (s *State) GetNodeByMachineKey(machineKey key.MachinePublic, userID types.UserID) (types.NodeView, bool) { + return s.nodeStore.GetNodeByMachineKey(machineKey, userID) } // ResolveNode looks up a node by numeric ID, IPv4/IPv6 address, given @@ -775,16 +688,6 @@ func (s *State) ResolveNode(query string) (types.NodeView, bool) { return s.GetNodeByID(id) } - // keepLowest returns whichever node has the lower ID, so repeated - // calls resolve deterministically across snapshot iterations. - keepLowest := func(cur, cand types.NodeView) types.NodeView { - if !cur.Valid() || cand.ID() < cur.ID() { - return cand - } - - return cur - } - // Try IP address. addr, addrErr := netip.ParseAddr(query) if addrErr == nil { @@ -795,7 +698,9 @@ func (s *State) ResolveNode(query string) (types.NodeView, bool) { continue } - match = keepLowest(match, n) + if !match.Valid() || n.ID() < match.ID() { + match = n + } } return match, match.Valid() @@ -807,9 +712,13 @@ func (s *State) ResolveNode(query string) (types.NodeView, bool) { for _, n := range s.ListNodes().All() { if n.GivenName() == query { - givenMatch = keepLowest(givenMatch, n) + if !givenMatch.Valid() || n.ID() < givenMatch.ID() { + givenMatch = n + } } else if n.Hostname() == query { - hostMatch = keepLowest(hostMatch, n) + if !hostMatch.Valid() || n.ID() < hostMatch.ID() { + hostMatch = n + } } } @@ -859,9 +768,8 @@ func (s *State) ListPeers(nodeID types.NodeID, peerIDs ...types.NodeID) views.Sl // For specific peerIDs, filter from all nodes. // This path is used for incremental updates (NodeAdded, NodeChanged) // where the caller already knows which peer IDs are involved. - // Peer visibility filtering happens in the mapper against the live - // policy (buildTailPeers and the shared visiblePeerIDs filter), because - // the snapshot peer map is not rebuilt on policy changes. + // The peer visibility filtering happens in the mapper's buildTailPeers + // via MatchersForNode/ReduceNodes. allNodes := s.nodeStore.ListNodes() nodeIDSet := make(map[types.NodeID]struct{}, len(peerIDs)) @@ -899,11 +807,11 @@ func (s *State) ListEphemeralNodes() views.Slice[types.NodeView] { // SetNodeExpiry updates the expiration time for a node. // If expiry is nil, the node's expiry is disabled (node will never expire). func (s *State) SetNodeExpiry(nodeID types.NodeID, expiry *time.Time) (types.NodeView, change.Change, error) { - // Update [NodeStore] before database to ensure consistency. The [NodeStore] update - // is blocking and will be the source of truth for the batcher. The database update - // must make the exact same change. If the database update fails, the [NodeStore] - // change will remain, but since we return an error, no change notification will be - // sent to the batcher, preventing inconsistent state propagation. + // Update NodeStore before database to ensure consistency. The NodeStore update is + // blocking and will be the source of truth for the batcher. The database update must + // make the exact same change. If the database update fails, the NodeStore change will + // remain, but since we return an error, no change notification will be sent to the + // batcher, preventing inconsistent state propagation. n, ok := s.nodeStore.UpdateNode(nodeID, func(node *types.Node) { node.Expiry = expiry }) @@ -969,9 +877,9 @@ func (s *State) SetNodeTags(nodeID types.NodeID, tags []string) (types.NodeView, // Log the operation logTagOperation(existingNode, validatedTags) - // Update [NodeStore] before database to ensure consistency. The [NodeStore] update - // is blocking and will be the source of truth for the batcher. The database update - // must make the exact same change. + // Update NodeStore before database to ensure consistency. The NodeStore update is + // blocking and will be the source of truth for the batcher. The database update must + // make the exact same change. n, ok := s.nodeStore.UpdateNode(nodeID, func(node *types.Node) { node.Tags = validatedTags // Tagged nodes are owned by their tags, not a user. @@ -1039,12 +947,9 @@ func (s *State) SetApprovedRoutes(nodeID types.NodeID, routes []netip.Prefix) (t // auto-sanitisation) and collisions error out rather than silently // bumping a user-facing label. See HOSTNAME.md for the CLI contract. func (s *State) RenameNode(nodeID types.NodeID, newName string) (types.NodeView, change.Change, error) { - // Validate the label AND that the resulting FQDN fits MaxHostnameLength: - // a valid 63-char label can still overflow under a long base_domain, and - // an unmappable name would break this node and its peers (issue #3346). - err := types.ValidateGivenName(newName, s.cfg.BaseDomain) + err := dnsname.ValidLabel(newName) if err != nil { - return types.NodeView{}, change.Change{}, fmt.Errorf("%w: %w", ErrGivenNameInvalid, err) + return types.NodeView{}, change.Change{}, fmt.Errorf("renaming node: %w", err) } view, err := s.nodeStore.SetGivenName(nodeID, newName) @@ -1069,7 +974,7 @@ func (s *State) BackfillNodeIPs() ([]string, error) { return nil, err } - // Refresh [NodeStore] after IP changes to ensure consistency + // Refresh NodeStore after IP changes to ensure consistency if len(changes) > 0 { nodes, err := s.db.ListNodes() if err != nil { @@ -1091,7 +996,7 @@ func (s *State) BackfillNodeIPs() ([]string, error) { node.Hostinfo.NetInfo = netInfo } // TODO(kradalby): This should just update the IP addresses, nothing else in the node store. - // We should avoid [NodeStore.PutNode] here. + // We should avoid PutNode here. _ = s.nodeStore.PutNode(*node) } } @@ -1156,7 +1061,7 @@ func (s *State) MatchersForNode(node types.NodeView) ([]matcher.Match, error) { } // NodeCapMap returns the policy-derived CapMap for the given node, suitable -// for merging into [tailcfg.Node.CapMap] when the node is rendered as self or +// for merging into tailcfg.Node.CapMap when the node is rendered as self or // as someone else's peer. func (s *State) NodeCapMap(id types.NodeID) tailcfg.NodeCapMap { return s.polMan.NodeCapMap(id) @@ -1198,9 +1103,8 @@ func (s *State) AutoApproveRoutes(nv types.NodeView) (change.Change, error) { Strs("routes.approved.new", util.PrefixesToString(approved)). Msg("Single node auto-approval detected route changes") - // Persist the auto-approved routes to database and [NodeStore] via - // [State.SetApprovedRoutes]. This ensures consistency between database - // and [NodeStore]. + // Persist the auto-approved routes to database and NodeStore via SetApprovedRoutes + // This ensures consistency between database and NodeStore _, c, err := s.SetApprovedRoutes(nv.ID(), approved) if err != nil { log.Error(). @@ -1318,64 +1222,35 @@ func (s *State) IsNodeHealthy(id types.NodeID) bool { return s.nodeStore.IsNodeHealthy(id) } -// SetNodeHealth flips the runtime health bit for one node and reports -// whether the resulting primary-route assignment changed, so the HA -// prober can decide whether to fan out a PolicyChange. true means -// healthy; false means unhealthy. An unhealthy mark is dropped when -// the node is no longer an HA candidate (offline or no approved -// routes) — between probe dispatch and result the node may have left -// candidacy, and the bit would just be stale. The check happens -// inside the writer goroutine so it serialises against the -// SetApprovedRoutes / Disconnect that removed candidacy. Single-node -// convenience wrapper around [State.BatchSetNodeHealth]. -func (s *State) SetNodeHealth(id types.NodeID, healthy bool) bool { - return s.BatchSetNodeHealth(map[types.NodeID]bool{id: healthy}) -} - -// BatchSetNodeHealth applies a set of health updates atomically: the -// election runs once after every flag has been flipped, so observers -// never see an intermediate snapshot. Returns true when the -// primary-route assignment differs from before the batch so callers -// can gate a single PolicyChange dispatch. Map value true = healthy; -// false = unhealthy (gated as in [State.SetNodeHealth]). +// SetNodeUnhealthy flips the runtime Unhealthy bit and reports whether +// the resulting primary route assignment changed, so the HA prober can +// decide whether to fan out a PolicyChange. // -// Per-call publication would let a writer applying two flips -// back-to-back elect a node that the next snapshot demotes, -// momentarily pointing peers at the wrong primary; the batched form -// closes that window. -func (s *State) BatchSetNodeHealth(updates map[types.NodeID]bool) bool { - if len(updates) == 0 { - return false - } - +// A request to mark a node Unhealthy is dropped if the node is no +// longer an HA candidate (offline or no approved routes). The prober +// reads HANodes() at the start of a probe cycle and writes back after +// the timeout fires; in that window the node may have left the +// candidate set, and the bit would just be stale. The check happens +// inside the writer goroutine so it serialises against the +// SetApprovedRoutes / Disconnect that removed candidacy. +func (s *State) SetNodeUnhealthy(id types.NodeID, unhealthy bool) bool { prevRoutes := s.nodeStore.PrimaryRoutes() - fns := make(map[types.NodeID]UpdateNodeFunc, len(updates)) - for id, healthy := range updates { - fns[id] = healthSetter(healthy) - } - - s.nodeStore.UpdateNodes(fns) - - return !maps.Equal(prevRoutes, s.nodeStore.PrimaryRoutes()) -} - -// healthSetter returns an UpdateNodeFunc that flips n.Unhealthy to -// the inverse of healthy, with the same gate as [State.SetNodeHealth]: -// an unhealthy mark only sticks when the node is still online and -// still advertises approved routes, so a node that left HA candidacy -// between probe dispatch and result does not carry a stale bit. -func healthSetter(healthy bool) UpdateNodeFunc { - return func(n *types.Node) { - if !healthy { + _, ok := s.nodeStore.UpdateNode(id, func(n *types.Node) { + if unhealthy { online := n.IsOnline != nil && *n.IsOnline if !online || len(n.AllApprovedRoutes()) == 0 { return } } - n.Unhealthy = !healthy + n.Unhealthy = unhealthy + }) + if !ok { + return false } + + return !maps.Equal(prevRoutes, s.nodeStore.PrimaryRoutes()) } // ValidateAPIKey checks if an API key is valid and active. @@ -1383,17 +1258,6 @@ func (s *State) ValidateAPIKey(keyStr string) (bool, error) { return s.db.ValidateAPIKey(keyStr) } -// AuthenticateAPIKey validates an API key and returns it (with its owning -// user), so callers like the v2 API can act as the key's owner. -func (s *State) AuthenticateAPIKey(keyStr string) (*types.APIKey, error) { - return s.db.AuthenticateAPIKey(keyStr) -} - -// SetAPIKeyUser sets the owning user of an API key by its database ID. -func (s *State) SetAPIKeyUser(keyID uint64, userID types.UserID) error { - return s.db.SetAPIKeyUser(keyID, userID) -} - // CreateAPIKey generates a new API key with optional expiration. func (s *State) CreateAPIKey(expiration *time.Time) (string, *types.APIKey, error) { return s.db.CreateAPIKey(expiration) @@ -1449,11 +1313,11 @@ func (s *State) CreateNodeForTest(user *types.User, hostname ...string) *types.N return s.db.CreateNodeForTest(user, hostname...) } -// PutNodeInStoreForTest writes a test node into the in-memory [NodeStore] -// so handlers backed by [NodeStore] lookups (e.g. [State.GetNodeByID]) can -// see it. [State.CreateNodeForTest] only saves to the database, which is -// fine for tests that exercise the DB layer directly but insufficient for -// handler tests that go through [State]. +// PutNodeInStoreForTest writes a test node into the in-memory NodeStore +// so handlers backed by NodeStore lookups (e.g. GetNodeByID) can see it. +// CreateNodeForTest only saves to the database, which is fine for tests +// that exercise the DB layer directly but insufficient for handler tests +// that go through State. func (s *State) PutNodeInStoreForTest(node types.Node) types.NodeView { return s.nodeStore.PutNode(node) } @@ -1478,27 +1342,9 @@ func (s *State) DB() *hsdb.HSDatabase { return s.db } -// GetPreAuthKey retrieves a pre-authentication key by its secret. The caller is -// responsible for checking whether the key is usable (expired or used). -func (s *State) GetPreAuthKey(keyStr string) (*types.PreAuthKey, error) { - return s.db.GetPreAuthKey(keyStr) -} - -// GetPreAuthKeyByID retrieves a pre-authentication key by its database id. -func (s *State) GetPreAuthKeyByID(id uint64) (*types.PreAuthKey, error) { - return s.db.GetPreAuthKeyByID(id) -} - -// RevokePreAuthKey soft-revokes a pre-authentication key: it is kept and stays -// retrievable (invalid) until the collector reaps it after the retention window. -func (s *State) RevokePreAuthKey(id uint64) error { - return s.db.RevokePreAuthKey(id) -} - -// DestroyRevokedPreAuthKeysBefore hard-deletes pre-auth keys revoked before -// cutoff, returning how many were removed. -func (s *State) DestroyRevokedPreAuthKeysBefore(cutoff time.Time) (int, error) { - return s.db.DestroyRevokedPreAuthKeysBefore(cutoff) +// GetPreAuthKey retrieves a pre-authentication key by ID. +func (s *State) GetPreAuthKey(id string) (*types.PreAuthKey, error) { + return s.db.GetPreAuthKey(id) } // ListPreAuthKeys returns all pre-authentication keys for a user. @@ -1506,11 +1352,6 @@ func (s *State) ListPreAuthKeys() ([]types.PreAuthKey, error) { return s.db.ListPreAuthKeys() } -// SetPreAuthKeyDescription sets the free-text description on a pre-auth key. -func (s *State) SetPreAuthKeyDescription(id uint64, description string) error { - return s.db.SetPreAuthKeyDescription(id, description) -} - // ExpirePreAuthKey marks a pre-authentication key as expired. func (s *State) ExpirePreAuthKey(id uint64) error { return s.db.ExpirePreAuthKey(id) @@ -1521,63 +1362,6 @@ func (s *State) DeletePreAuthKey(id uint64) error { return s.db.DeletePreAuthKey(id) } -// CreateOAuthClient creates a new OAuth client-credentials client, returning the -// plaintext secret (shown once) and the stored client. -func (s *State) CreateOAuthClient(scopes, tags []string, description string, creatorUserID *uint) (string, *types.OAuthClient, error) { - return s.db.CreateOAuthClient(scopes, tags, description, creatorUserID) -} - -// AuthenticateOAuthClient validates a client secret and returns the client. -func (s *State) AuthenticateOAuthClient(secret string) (*types.OAuthClient, error) { - return s.db.AuthenticateOAuthClient(secret) -} - -// GetOAuthClientByClientID returns an OAuth client by its public client id. -func (s *State) GetOAuthClientByClientID(clientID string) (*types.OAuthClient, error) { - return s.db.GetOAuthClientByClientID(clientID) -} - -// ListOAuthClients returns every OAuth client. -func (s *State) ListOAuthClients() ([]types.OAuthClient, error) { - return s.db.ListOAuthClients() -} - -// RevokeOAuthClient deletes a client and the access tokens it issued. -func (s *State) RevokeOAuthClient(clientID string) error { - return s.db.RevokeOAuthClient(clientID) -} - -// MintAccessToken stores a new scoped access token for an OAuth client. -func (s *State) MintAccessToken(clientID string, scopes, tags []string, expiration *time.Time) (string, *types.OAuthAccessToken, error) { - return s.db.MintAccessToken(clientID, scopes, tags, expiration) -} - -// AuthenticateAccessToken validates a bearer access token and returns it with -// its granted scopes and tags. -func (s *State) AuthenticateAccessToken(token string) (*types.OAuthAccessToken, error) { - return s.db.AuthenticateAccessToken(token) -} - -// TagOwnedByTags reports whether a credential holding ownerTags may apply tag, -// per the policy's tag-to-tag ownership. Used to authorise the tags an OAuth -// access token sets on the auth keys it mints. -func (s *State) TagOwnedByTags(tag string, ownerTags []string) bool { - return s.polMan.TagOwnedByTags(tag, ownerTags) -} - -// TagExists reports whether tag is defined in the policy's tagOwners. Used to -// reject OAuth clients and auth keys carrying tags that no policy authorises, -// matching SetNodeTags. -func (s *State) TagExists(tag string) bool { - return s.polMan.TagExists(tag) -} - -// DeleteExpiredAccessTokens hard-deletes OAuth access tokens that expired before -// cutoff, returning how many were removed. -func (s *State) DeleteExpiredAccessTokens(cutoff time.Time) (int64, error) { - return s.db.DeleteExpiredAccessTokens(cutoff) -} - // GetAuthCacheEntry retrieves a pending auth request from the cache. func (s *State) GetAuthCacheEntry(id types.AuthID) (*types.AuthRequest, bool) { return s.authCache.Get(id) @@ -1588,14 +1372,6 @@ func (s *State) SetAuthCacheEntry(id types.AuthID, entry *types.AuthRequest) { s.authCache.Add(id, entry) } -// DeleteAuthCacheEntryForTest drops a pending auth request from the cache, -// exposed for testing so a test can reproduce a session that was lost -// (expired, evicted, or dropped on a control-plane restart) without faking an -// auth_id. -func (s *State) DeleteAuthCacheEntryForTest(id types.AuthID) { - s.authCache.Remove(id) -} - // SetLastSSHAuth records a successful SSH check authentication // for the given (src, dst) node pair. func (s *State) SetLastSSHAuth(src, dst types.NodeID) { @@ -1657,7 +1433,7 @@ type newNodeParams struct { // authNodeUpdateParams contains parameters for updating an existing node during auth. type authNodeUpdateParams struct { - // Node to update; must be valid and in [NodeStore]. + // Node to update; must be valid and in NodeStore. ExistingNode types.NodeView // Cached registration payload from the originating client request. RegData *types.RegistrationData @@ -1676,7 +1452,7 @@ type authNodeUpdateParams struct { } // applyAuthNodeUpdate applies common update logic for re-authenticating or converting -// an existing node. It updates the node in [NodeStore], processes RequestTags, and +// an existing node. It updates the node in NodeStore, processes RequestTags, and // persists changes to the database. func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView, error) { regData := params.RegData @@ -1704,9 +1480,8 @@ func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView oldTags := params.ExistingNode.Tags().AsSlice() - // Validate tags BEFORE calling [NodeStore.UpdateNode] to ensure we don't modify - // [NodeStore] if validation fails. This maintains consistency between [NodeStore] - // and database. + // Validate tags BEFORE calling UpdateNode to ensure we don't modify NodeStore + // if validation fails. This maintains consistency between NodeStore and database. rejectedTags := s.validateRequestTags(params.ExistingNode, requestTags) if len(rejectedTags) > 0 { return types.NodeView{}, fmt.Errorf( @@ -1716,18 +1491,7 @@ func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView ) } - // Re-auth rotates the NodeKey to the client-supplied value. Enforce the - // same 1:1 NodeKey<->MachineKey binding createAndSaveNewNode applies at - // registration and getAndValidateNode enforces at poll time: a NodeKey - // already bound to a different machine must not be claimed here, or a - // re-authenticating node could rotate its key to a victim's and poison - // the NodeStore NodeKey index (denying the victim service). - if existing, ok := s.nodeStore.GetNodeByNodeKey(regData.NodeKey); ok && - existing.MachineKey() != regData.MachineKey { - return types.NodeView{}, ErrNodeKeyInUse - } - - // Update existing node in [NodeStore] - validation passed, safe to mutate + // Update existing node in NodeStore - validation passed, safe to mutate updatedNodeView, ok := s.nodeStore.UpdateNode(params.ExistingNode.ID(), func(node *types.Node) { node.NodeKey = regData.NodeKey node.DiscoKey = regData.DiscoKey @@ -1741,20 +1505,12 @@ func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView params.ValidHostinfo, ) - // Preserve the node's live endpoints when the register request carried - // none. Web/OIDC relogins report endpoints via MapRequest, not register, - // so RegData.Endpoints is empty; clearing the stored set would advertise - // the re-keyed node with no way for peers to reach it. The first - // MapRequest restores the live set. - if len(regData.Endpoints) > 0 { - node.Endpoints = regData.Endpoints - } + node.Endpoints = regData.Endpoints // Do NOT reset IsOnline here. Online status is managed exclusively by - // [State.Connect]/[State.Disconnect] in the poll session lifecycle. - // Resetting it during re-registration causes a false offline blip: the - // change notification triggers a map regeneration showing the node as - // offline to peers, even though [State.Connect] will immediately set it - // back to true. + // Connect()/Disconnect() in the poll session lifecycle. Resetting it + // during re-registration causes a false offline blip: the change + // notification triggers a map regeneration showing the node as offline + // to peers, even though Connect() will immediately set it back to true. node.LastSeen = new(time.Now()) // On conversion (tagged → user) we set the new register method. @@ -1787,7 +1543,7 @@ func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView case !wasTagged && isTagged: // Personal → Tagged: clear expiry (tagged nodes don't expire) node.Expiry = nil - case params.IsConvertFromTag && !isTagged: + case params.IsConvertFromTag: // Explicit conversion from tagged to user-owned: set expiry from client request if params.Expiry != nil { node.Expiry = params.Expiry @@ -1849,7 +1605,7 @@ func (s *State) applyAuthNodeUpdate(params authNodeUpdateParams) (types.NodeView return updatedNodeView, nil } -// createAndSaveNewNode creates a new node, allocates IPs, saves to DB, and adds to [NodeStore]. +// createAndSaveNewNode creates a new node, allocates IPs, saves to DB, and adds to NodeStore. // It preserves netinfo from an existing node if one is provided (for faster DERP connectivity). func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, error) { // Preserve NetInfo from existing node if available @@ -1861,20 +1617,6 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro ) } - // Enforce NodeKey uniqueness across machines. NodeKeys are public - // (peers learn them from the netmap), so an authenticated party could - // otherwise register a node carrying a victim's NodeKey, poisoning the - // NodeStore NodeKey index so the victim's MapRequest resolves to the - // wrong node and is rejected by getAndValidateNode's MachineKey check - // (a DoS). createAndSaveNewNode only runs for a machine that has no - // existing node, so any current holder of this NodeKey is a different - // machine; mirror the 1:1 binding getAndValidateNode enforces at poll - // time and reject before allocating any resources. - if existing, ok := s.nodeStore.GetNodeByNodeKey(params.NodeKey); ok && - existing.MachineKey() != params.MachineKey { - return types.NodeView{}, ErrNodeKeyInUse - } - // Prepare the node for registration nodeToRegister := types.Node{ Hostname: params.Hostname, @@ -1884,7 +1626,7 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro Hostinfo: params.Hostinfo, Endpoints: params.Endpoints, LastSeen: new(time.Now()), - IsOnline: new(false), // Explicitly offline until [State.Connect] is called + IsOnline: new(false), // Explicitly offline until Connect() is called RegisterMethod: params.RegisterMethod, Expiry: params.Expiry, } @@ -1894,7 +1636,7 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro if params.PreAuthKey.IsTagged() { // Tagged nodes are owned by their tags, not a user. // UserID is intentionally left nil. - nodeToRegister.Tags = params.PreAuthKey.Tags + nodeToRegister.Tags = params.PreAuthKey.Proto().GetAclTags() // Tagged nodes have key expiry disabled. nodeToRegister.Expiry = nil @@ -1977,14 +1719,14 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro nodeToRegister.IPv4 = ipv4 nodeToRegister.IPv6 = ipv6 - // Seed GivenName from the sanitised raw hostname. [NodeStore.PutNode] + // Seed GivenName from the sanitised raw hostname. NodeStore.PutNode // bumps on collision and falls back to "node" if the sanitised // result is empty (pure non-ASCII / punctuation input). if nodeToRegister.GivenName == "" { nodeToRegister.GivenName = dnsname.SanitizeHostname(nodeToRegister.Hostname) } - // New node - database first to get ID, then [NodeStore] + // New node - database first to get ID, then NodeStore savedNode, err := hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) { err := tx.Save(&nodeToRegister).Error if err != nil { @@ -2004,12 +1746,12 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro return types.NodeView{}, err } - // Add to [NodeStore] after database creates the ID + // Add to NodeStore after database creates the ID return s.nodeStore.PutNode(*savedNode), nil } // validateRequestTags validates that the requested tags are permitted for the node. -// This should be called BEFORE [NodeStore.UpdateNode] to ensure we don't modify [NodeStore] +// This should be called BEFORE UpdateNode to ensure we don't modify NodeStore // if validation fails. Returns the list of rejected tags (empty if all valid). func (s *State) validateRequestTags(node types.NodeView, requestTags []string) []string { // Empty tags = clear tags, always permitted @@ -2154,37 +1896,16 @@ func (s *State) HandleNodeFromAuthPath( // Lookup existing nodes machineKey := regData.MachineKey + existingNodeSameUser, _ := s.nodeStore.GetNodeByMachineKey(machineKey, types.UserID(user.ID)) + existingNodeAnyUser, _ := s.nodeStore.GetNodeByMachineKeyAnyUser(machineKey) - // Serialise registration for this machine so concurrent auth callbacks - // resolve to a single node rather than racing the find-then-create section. - defer s.lockRegistration(machineKey)() - - all := s.nodeStore.GetNodesByMachineKeyAllUsers(machineKey) - - // Named conditions - describe WHAT we found, not HOW we check it. - existingNodeSameUser, nodeExistsForSameUser := all[types.UserID(user.ID)] - - taggedNode, hasTagged := all[0] - existingNodeIsTagged := hasTagged && taggedNode.IsTagged() - - var existingNodeOtherUser types.NodeView - - existingNodeOwnedByOtherUser := false - - for uid, n := range all { - if uid != 0 && uid != types.UserID(user.ID) && !n.IsTagged() { - existingNodeOtherUser = n - existingNodeOwnedByOtherUser = true - } - } - - // A tagged node and a user-owned node cannot legitimately share a machine - // key (validateNodeOwnership enforces tags XOR user ownership). If both are - // present the machine key is in a corrupt/ambiguous state; reject rather - // than converting an arbitrary node and orphaning the other. - if existingNodeIsTagged && (nodeExistsForSameUser || existingNodeOwnedByOtherUser) { - return types.NodeView{}, change.Change{}, ErrAmbiguousNodeOwnership - } + // Named conditions - describe WHAT we found, not HOW we check it + nodeExistsForSameUser := existingNodeSameUser.Valid() + nodeExistsForAnyUser := existingNodeAnyUser.Valid() + existingNodeIsTagged := nodeExistsForAnyUser && existingNodeAnyUser.IsTagged() + existingNodeOwnedByOtherUser := nodeExistsForAnyUser && + !existingNodeIsTagged && + existingNodeAnyUser.UserID().Get() != user.ID // Create logger with common fields for all auth operations logger := log.With(). @@ -2214,7 +1935,7 @@ func (s *State) HandleNodeFromAuthPath( return types.NodeView{}, change.Change{}, err } } else if existingNodeIsTagged { - updateParams.ExistingNode = taggedNode + updateParams.ExistingNode = existingNodeAnyUser updateParams.IsConvertFromTag = true finalNode, err = s.applyAuthNodeUpdate(updateParams) @@ -2222,7 +1943,7 @@ func (s *State) HandleNodeFromAuthPath( return types.NodeView{}, change.Change{}, err } } else if existingNodeOwnedByOtherUser { - oldUser := existingNodeOtherUser.User() + oldUser := existingNodeAnyUser.User() oldUserName := "" if oldUser.Valid() { @@ -2230,14 +1951,14 @@ func (s *State) HandleNodeFromAuthPath( } logger.Info(). - Str(zf.ExistingNodeName, existingNodeOtherUser.Hostname()). - Uint64(zf.ExistingNodeID, existingNodeOtherUser.ID().Uint64()). + Str(zf.ExistingNodeName, existingNodeAnyUser.Hostname()). + Uint64(zf.ExistingNodeID, existingNodeAnyUser.ID().Uint64()). Str(zf.OldUser, oldUserName). Msg("Creating new node for different user (same machine key exists for another user)") finalNode, err = s.createNewNodeFromAuth( logger, user, regData, hostname, hostinfo, - expiry, registrationMethod, existingNodeOtherUser, + expiry, registrationMethod, existingNodeAnyUser, ) if err != nil { return types.NodeView{}, change.Change{}, err @@ -2269,12 +1990,14 @@ func (s *State) HandleNodeFromAuthPath( return finalNode, change.NodeAdded(finalNode.ID()), fmt.Errorf("updating policy manager nodes: %w", err) } - policyChanged := !usersChange.IsEmpty() || !nodesChange.IsEmpty() + var c change.Change + if !usersChange.IsEmpty() || !nodesChange.IsEmpty() { + c = change.PolicyChange() + } else { + c = change.NodeAdded(finalNode.ID()) + } - // nodeExistsForSameUser is true only for a same-user relogin; a tag->user - // conversion is excluded, as it changes the peer's User — a structural - // change peers must see in full, not a key-rotation patch. - return finalNode, reauthChange(finalNode, nodeExistsForSameUser, policyChanged), nil + return finalNode, c, nil } // createNewNodeFromAuth creates a new node during auth callback. @@ -2316,71 +2039,27 @@ func (s *State) createNewNodeFromAuth( func (s *State) findExistingNodeForPAK( machineKey key.MachinePublic, pak *types.PreAuthKey, -) (types.NodeView, bool, error) { - all := s.nodeStore.GetNodesByMachineKeyAllUsers(machineKey) - +) (types.NodeView, bool) { if pak.User != nil { - if node, ok := all[types.UserID(pak.User.ID)]; ok { - return node, true, nil + node, exists := s.nodeStore.GetNodeByMachineKey(machineKey, types.UserID(pak.User.ID)) + if exists { + return node, true } - - // The node may have been converted to a tagged node since it first - // registered (SetNodeTags clears UserID, re-indexing it under UserID(0)). - // It is still the same machine, proven by the machine key, so recognise - // it for re-registration instead of re-validating the spent key or - // creating a duplicate node. Re-registration preserves the node's tagged - // ownership. See https://github.com/juanfont/headscale/issues/3312. - if node, ok := all[0]; ok && node.IsTagged() { - return node, true, nil - } - - return types.NodeView{}, false, nil } - // A tagged key re-registers the same machine regardless of how it is - // currently owned. An existing tagged node is a plain re-registration. A - // single user-owned node is converted to tagged in place (handled by the - // caller). More than one user-owned node is ambiguous - we cannot know - // which to convert - so reject rather than convert an arbitrary one and - // orphan the rest. + // Tagged nodes have nil UserID, so they are indexed under UserID(0) + // in nodesByMachineKey. Check there for tagged PAK re-registration. if pak.IsTagged() { - if node, ok := all[0]; ok && node.IsTagged() { - return node, true, nil - } - - var userOwned types.NodeView - - count := 0 - - for uid, node := range all { - if uid != 0 && !node.IsTagged() { - userOwned = node - count++ - } - } - - switch count { - case 0: - return types.NodeView{}, false, nil - case 1: - return userOwned, true, nil - default: - return types.NodeView{}, false, ErrAmbiguousNodeOwnership - } + return s.nodeStore.GetNodeByMachineKey(machineKey, 0) } - return types.NodeView{}, false, nil + return types.NodeView{}, false } -//nolint:gocyclo // sequential validation/update/create paths with security-sensitive ordering func (s *State) HandleNodeFromPreAuthKey( regReq tailcfg.RegisterRequest, machineKey key.MachinePublic, ) (types.NodeView, change.Change, error) { - // Serialise registration for this machine so concurrent restarts resolve - // to a single node rather than racing the find-then-create section. - defer s.lockRegistration(machineKey)() - pak, err := s.GetPreAuthKey(regReq.Auth.AuthKey) if err != nil { return types.NodeView{}, change.Change{}, err @@ -2395,10 +2074,7 @@ func (s *State) HandleNodeFromPreAuthKey( return types.TaggedDevices.Name } - existingNodeSameUser, existsSameUser, err := s.findExistingNodeForPAK(machineKey, pak) - if err != nil { - return types.NodeView{}, change.Change{}, err - } + existingNodeSameUser, existsSameUser := s.findExistingNodeForPAK(machineKey, pak) // For existing nodes, skip validation if: // 1. MachineKey matches (cryptographic proof of machine identity) @@ -2417,24 +2093,10 @@ func (s *State) HandleNodeFromPreAuthKey( isNodeKeyRotation := existsSameUser && existingNodeSameUser.Valid() && existingNodeSameUser.NodeKey() != regReq.NodeKey - // An expired node is genuinely re-authenticating, not just waking up, so it - // must present a valid key. Without this a node that re-uses its NodeKey - // after expiry would skip validation and be re-authorised with a spent or - // expired key; the boundary must not depend on the client rotating its key. - isExpired := existsSameUser && existingNodeSameUser.Valid() && - existingNodeSameUser.IsExpired() - - // A tagged key presented for a currently user-owned node converts that node - // to tagged. That is an ownership change, not a plain refresh, so it must - // present a valid key rather than ride the skip-validation fast-path. - isOwnershipConversion := existsSameUser && existingNodeSameUser.Valid() && - pak.IsTagged() && !existingNodeSameUser.IsTagged() - - if isExistingNodeReregistering && !isNodeKeyRotation && !isExpired && !isOwnershipConversion { - // Existing, still-valid node re-registering with same NodeKey: skip - // validation. Pre-auth keys are only needed for initial authentication. - // Critical for containers that run "tailscale up --authkey=KEY" on every - // restart. + if isExistingNodeReregistering && !isNodeKeyRotation { + // Existing node re-registering with same NodeKey: skip validation. + // Pre-auth keys are only needed for initial authentication. Critical for + // containers that run "tailscale up --authkey=KEY" on every restart. log.Debug(). Caller(). Uint64(zf.NodeID, existingNodeSameUser.ID().Uint64()). @@ -2490,23 +2152,6 @@ func (s *State) HandleNodeFromPreAuthKey( Str(zf.UserName, pakUsername()). Msg("Node re-registering with existing machine key and user, updating in place") - // Re-registration rotates the NodeKey to the client-supplied value. - // Enforce the same 1:1 NodeKey<->MachineKey binding the auth path - // (applyAuthNodeUpdate) and poll-time validation enforce: a NodeKey - // already bound to a different machine must not be claimed here, or a - // re-registering node could rotate its key to a victim's and poison the - // NodeStore NodeKey index, denying the victim service. - if existing, ok := s.nodeStore.GetNodeByNodeKey(regReq.NodeKey); ok && - existing.MachineKey() != machineKey { - return types.NodeView{}, change.Change{}, ErrNodeKeyInUse - } - - // Snapshot the pre-update node so the NodeStore can be rolled back if - // the database write below fails. The view points at the immutable - // pre-update snapshot (UpdateNode swaps in a new one), so this stays - // valid after the mutation. - priorNode := existingNodeSameUser.AsStruct() - // Update existing node - NodeStore first, then database updatedNodeView, ok := s.nodeStore.UpdateNode(existingNodeSameUser.ID(), func(node *types.Node) { node.NodeKey = regReq.NodeKey @@ -2522,24 +2167,13 @@ func (s *State) HandleNodeFromPreAuthKey( node.RegisterMethod = util.RegisterMethodAuthKey // Tags from PreAuthKey are only applied during initial registration. - // On re-registration the node keeps its existing tags and ownership, - // except when a tagged key converts a user-owned node: that adopts - // the key's tags and drops user ownership (tagged nodes are - // user-less and never expire). Only update AuthKey reference - // otherwise. - if pak.IsTagged() && !node.IsTagged() { - node.Tags = pak.Tags - node.UserID = nil - node.User = nil - node.Expiry = nil - } - + // On re-registration the node keeps its existing tags and ownership. + // Only update AuthKey reference. node.AuthKey = pak node.AuthKeyID = &pak.ID // Do NOT reset IsOnline here. Online status is managed exclusively by - // [State.Connect]/[State.Disconnect] in the poll session lifecycle. - // Resetting it during re-registration causes a false offline blip - // to peers. + // Connect()/Disconnect() in the poll session lifecycle. Resetting it + // during re-registration causes a false offline blip to peers. node.LastSeen = new(time.Now()) // Tagged nodes keep their existing expiry (disabled). @@ -2574,7 +2208,7 @@ func (s *State) HandleNodeFromPreAuthKey( // Only mark the key used on the *first* registration. On // re-registration the same key is already used and the - // atomic compare-and-set in [hsdb.UsePreAuthKey] would otherwise + // atomic compare-and-set in UsePreAuthKey would otherwise // reject it as "authkey already used". This is the path // behind issue #2830 where containers restart with the // same one-shot key. @@ -2588,13 +2222,6 @@ func (s *State) HandleNodeFromPreAuthKey( return nil, nil //nolint:nilnil // intentional: transaction success }) if err != nil { - // The NodeStore was updated before the database write. Roll it back - // so it does not advertise a registration the database rejected - // (e.g. a node key that a restart would not reload). - if priorNode != nil { - s.nodeStore.PutNode(*priorNode) - } - return types.NodeView{}, change.Change{}, fmt.Errorf("writing node to database: %w", err) } @@ -2609,29 +2236,24 @@ func (s *State) HandleNodeFromPreAuthKey( finalNode = updatedNodeView } else { - // Node does not exist for this user with this machine key. - // For a user-owned key, check whether the machine key is already held - // by a node belonging to a different user (tags-only keys skip this; - // tagged nodes have no owning user). Any such node yields the same - // outcome - create a new node for the new user, do not transfer - so a - // single representative is enough. - var differentUserNode types.NodeView + // Node does not exist for this user with this machine key + // Check if node exists with this machine key for a different user + existingNodeAnyUser, existsAnyUser := s.nodeStore.GetNodeByMachineKeyAnyUser(machineKey) - belongsToDifferentUser := false - - if pak.User != nil { - for uid, node := range s.nodeStore.GetNodesByMachineKeyAllUsers(machineKey) { - if uid != 0 && !node.IsTagged() && uid != types.UserID(pak.User.ID) { - differentUserNode = node - belongsToDifferentUser = true - } - } - } + // For user-owned keys, check if node exists for a different user. + // Tags-only keys (pak.User == nil) skip this check. + // Tagged nodes are also skipped since they have no owning user. + existingIsUserOwned := existsAnyUser && + existingNodeAnyUser.Valid() && + !existingNodeAnyUser.IsTagged() + belongsToDifferentUser := pak.User != nil && + existingIsUserOwned && + existingNodeAnyUser.UserID().Get() != pak.User.ID if belongsToDifferentUser { // Node exists but belongs to a different user. // Create a new node for the new user (do not transfer). - oldUser := differentUserNode.User() + oldUser := existingNodeAnyUser.User() oldUserName := "" if oldUser.Valid() { @@ -2640,8 +2262,8 @@ func (s *State) HandleNodeFromPreAuthKey( log.Info(). Caller(). - Str(zf.ExistingNodeName, differentUserNode.Hostname()). - Uint64(zf.ExistingNodeID, differentUserNode.ID().Uint64()). + Str(zf.ExistingNodeName, existingNodeAnyUser.Hostname()). + Uint64(zf.ExistingNodeID, existingNodeAnyUser.ID().Uint64()). Str(zf.MachineKey, machineKey.ShortString()). Str(zf.OldUser, oldUserName). Str(zf.NewUser, pakUsername()). @@ -2681,7 +2303,7 @@ func (s *State) HandleNodeFromPreAuthKey( Expiry: reqExpiry, RegisterMethod: util.RegisterMethodAuthKey, PreAuthKey: pak, - ExistingNodeForNetinfo: differentUserNode, + ExistingNodeForNetinfo: cmp.Or(existingNodeAnyUser, types.NodeView{}), }) if err != nil { return types.NodeView{}, change.Change{}, fmt.Errorf("creating new node: %w", err) @@ -2699,27 +2321,14 @@ func (s *State) HandleNodeFromPreAuthKey( return finalNode, change.NodeAdded(finalNode.ID()), fmt.Errorf("updating policy manager nodes: %w", err) } - policyChanged := !usersChange.IsEmpty() || !nodesChange.IsEmpty() - - return finalNode, reauthChange(finalNode, existsSameUser, policyChanged), nil -} - -// reauthChange returns the [change.Change] to broadcast after an authentication -// that updated or created a node. -// -// A pure relogin (isRelogin: an existing node, same user, with only its NodeKey -// rotated) is sent as a minimal incremental peer patch via [change.NodeKeyRotated] -// rather than re-advertising the whole node. A policy change forces a full -// recompute; any other (new) node is a whole-node add. -func reauthChange(node types.NodeView, isRelogin, policyChanged bool) change.Change { - switch { - case policyChanged: - return change.PolicyChange() - case isRelogin: - return change.NodeKeyRotated(node) - default: - return change.NodeAdded(node.ID()) + var c change.Change + if !usersChange.IsEmpty() || !nodesChange.IsEmpty() { + c = change.PolicyChange() + } else { + c = change.NodeAdded(finalNode.ID()) } + + return finalNode, c, nil } // updatePolicyManagerUsers updates the policy manager with current users. @@ -2796,75 +2405,51 @@ func (s *State) PingDB(ctx context.Context) error { func (s *State) autoApproveNodes() ([]change.Change, error) { nodes := s.ListNodes() - // Compute every node's approval first, then apply them all in a single - // NodeStore batch and a single policy/peer-map rebuild. One - // SetApprovedRoutes per node would otherwise drive an O(n) policy SetNodes - // and O(n^2) peer-map rebuild for each changed node, i.e. O(m*n^2) per - // policy reload. - approvedByID := make(map[types.NodeID][]netip.Prefix) - + // Approve routes concurrently, this should make it likely + // that the writes end in the same batch in the nodestore write. + var ( + errg errgroup.Group + cs []change.Change + mu sync.Mutex + ) for _, nv := range nodes.All() { - approved, changed := policy.ApproveRoutesWithPolicy(s.polMan, nv, nv.ApprovedRoutes().AsSlice(), nv.AnnouncedRoutes()) - if !changed { - continue - } + errg.Go(func() error { + approved, changed := policy.ApproveRoutesWithPolicy(s.polMan, nv, nv.ApprovedRoutes().AsSlice(), nv.AnnouncedRoutes()) + if changed { + log.Debug(). + Uint64(zf.NodeID, nv.ID().Uint64()). + Str(zf.NodeName, nv.Hostname()). + Strs(zf.RoutesApprovedOld, util.PrefixesToString(nv.ApprovedRoutes().AsSlice())). + Strs(zf.RoutesApprovedNew, util.PrefixesToString(approved)). + Msg("Routes auto-approved by policy") - log.Debug(). - Uint64(zf.NodeID, nv.ID().Uint64()). - Str(zf.NodeName, nv.Hostname()). - Strs(zf.RoutesApprovedOld, util.PrefixesToString(nv.ApprovedRoutes().AsSlice())). - Strs(zf.RoutesApprovedNew, util.PrefixesToString(approved)). - Msg("Routes auto-approved by policy") + _, c, err := s.SetApprovedRoutes(nv.ID(), approved) + if err != nil { + return err + } - approvedByID[nv.ID()] = approved - } + mu.Lock() - if len(approvedByID) == 0 { - return nil, nil - } + cs = append(cs, c) - updates := make(map[types.NodeID]UpdateNodeFunc, len(approvedByID)) - for id, approved := range approvedByID { - updates[id] = func(n *types.Node) { - n.ApprovedRoutes = approved - - // A node with no approved routes is no longer an HA candidate; - // drop any stale Unhealthy bit (mirrors SetApprovedRoutes). - if len(n.AllApprovedRoutes()) == 0 { - n.Unhealthy = false + mu.Unlock() } - } + + return nil + }) } - s.nodeStore.UpdateNodes(updates) - - for id := range approvedByID { - fresh, ok := s.nodeStore.GetNode(id) - if !ok { - continue - } - - _, err := s.persistNodeRowToDB(fresh) - if err != nil { - return nil, err - } - } - - c, err := s.updatePolicyManagerNodes() + err := errg.Wait() if err != nil { return nil, err } - if c.IsEmpty() { - c = change.PolicyChange() - } - - return []change.Change{c}, nil + return cs, nil } // isAutoDerivedGivenName reports whether given matches what // dnsname.SanitizeHostname(hostname) would produce, optionally with a -// [NodeStore] collision-bump "-N" suffix. It is used to detect whether a +// NodeStore collision-bump "-N" suffix. It is used to detect whether a // GivenName has been admin-renamed (in which case it must not be // overwritten by client-side hostname changes). func isAutoDerivedGivenName(given, hostname string) bool { @@ -2884,10 +2469,10 @@ func isAutoDerivedGivenName(given, hostname string) bool { } // UpdateNodeFromMapRequest is the sync point where Hostinfo changes, -// endpoint updates, and route advertisements from a [tailcfg.MapRequest] -// land in the [NodeStore]. It produces a [change.Change] summarising -// what actually moved so downstream subsystems (mapper, policy, primary -// routes) can react accordingly. +// endpoint updates, and route advertisements from a MapRequest land in +// the NodeStore. It produces a change.Change summarising what actually +// moved so downstream subsystems (mapper, policy, primary routes) can +// react accordingly. // // TODO(kradalby): This is essentially a patch update that could be sent directly to nodes, // which means we could shortcut the whole change thing if there are no other important updates. @@ -2895,7 +2480,7 @@ func isAutoDerivedGivenName(given, hostname string) bool { // - node.PeerChangeFromMapRequest // - node.ApplyPeerChange // - logTracePeerChange in poll.go. -func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest) (change.Change, error) { //nolint:gocyclo // central map-request reconciliation; the sequential branch flow reads clearer as one function than split across helpers +func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest) (change.Change, error) { log.Trace(). Caller(). Uint64(zf.NodeID, id.Uint64()). @@ -2909,24 +2494,18 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest autoApprovedRoutes []netip.Prefix endpointChanged bool derpChanged bool - persistWorthy bool ) // Snapshot the primary assignment so we can tell whether the // Hostinfo + auto-approval that follows shifted any prefix. prevRoutes := s.nodeStore.PrimaryRoutes() - // We need to ensure we update the node as it is in the [NodeStore] at + // We need to ensure we update the node as it is in the NodeStore at // the time of the request. updatedNode, ok := s.nodeStore.UpdateNode(id, func(currentNode *types.Node) { peerChange := currentNode.PeerChangeFromMapRequest(req) - // Track what specifically changed. An endpoint delta is only - // broadcast-worthy when it adds a useful (non-STUN) endpoint; - // STUN-only churn and pure shrinks are suppressed to reduce peer - // churn (see endpointBroadcastWorthy). The new set is still stored - // via ApplyPeerChange below regardless of this decision. - endpointChanged = peerChange.Endpoints != nil && - endpointBroadcastWorthy(currentNode.Endpoints, req.Endpoints, req.EndpointTypes) + // Track what specifically changed + endpointChanged = peerChange.Endpoints != nil derpChanged = peerChange.DERPRegion != 0 hostinfoChanged = !hostinfoEqual(currentNode.View(), req.Hostinfo) @@ -2941,20 +2520,13 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest // Re-check hostinfoChanged after potential NetInfo preservation hostinfoChanged = !hostinfoEqual(currentNode.View(), req.Hostinfo) - // A change carrying only an updated LastSeen is not worth a full-row - // database UPDATE plus the O(n) policy rescan persistNodeToDB triggers: - // LastSeen is best-effort and rides along the next substantive write. - // PeerChangeFromMapRequest always stamps LastSeen, so test the other - // fields explicitly. - persistWorthy = peerChangePersistWorthy(peerChange) || hostinfoChanged - // If there is no changes and nothing to save, // return early. if peerChangeEmpty(peerChange) && !hostinfoChanged { return } - // Calculate route approval before [NodeStore] update to avoid calling View() inside callback + // Calculate route approval before NodeStore update to avoid calling View() inside callback var hasNewRoutes bool if hi := req.Hostinfo; hi != nil { hasNewRoutes = len(hi.RoutableIPs) > 0 @@ -3016,7 +2588,7 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest currentNode.Hostname = req.Hostinfo.Hostname if autoDerived { currentNode.GivenName = dnsname.SanitizeHostname(req.Hostinfo.Hostname) - // [NodeStore.UpdateNode] auto-bumps GivenName on collision. + // NodeStore.UpdateNode auto-bumps GivenName on collision. } } @@ -3054,13 +2626,13 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest Strs(zf.AutoApprovedRoutes, util.PrefixesToString(autoApprovedRoutes)). Msg("Persisting auto-approved routes from MapRequest") - // [State.SetApprovedRoutes] will update both database and PrimaryRoutes table + // SetApprovedRoutes will update both database and PrimaryRoutes table _, c, err := s.SetApprovedRoutes(id, autoApprovedRoutes) if err != nil { return change.Change{}, fmt.Errorf("persisting auto-approved routes: %w", err) } - // If [State.SetApprovedRoutes] resulted in a policy change, return it + // If SetApprovedRoutes resulted in a policy change, return it if !c.IsEmpty() { return c, nil } @@ -3083,18 +2655,9 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest nodeRouteChange = change.PolicyChange() } - // A no-op MapRequest (identical re-send / reconnect with matching state) - // leaves the node untouched, so skip the full-row UPDATE and the O(n) - // policy SetNodes scan that persistNodeToDB performs. - policyChange := change.Change{} - - if persistWorthy { - var err error - - _, policyChange, err = s.persistNodeToDB(updatedNode) - if err != nil { - return change.Change{}, fmt.Errorf("saving to database: %w", err) - } + _, policyChange, err := s.persistNodeToDB(updatedNode) + if err != nil { + return change.Change{}, fmt.Errorf("saving to database: %w", err) } if !policyChange.IsEmpty() { @@ -3110,64 +2673,7 @@ func (s *State) UpdateNodeFromMapRequest(id types.NodeID, req tailcfg.MapRequest return buildMapRequestChangeResponse(id, updatedNode, hostinfoChanged, endpointChanged, derpChanged) } -// endpointBroadcastWorthy reports whether an endpoint-only delta is worth -// fanning out to peers as an incremental PeersChangedPatch. A delta that only -// adds STUN-derived endpoints — or only removes endpoints — is suppressed: -// bare STUN endpoints are unlikely to be open and churn a lot (the client -// re-derives those paths over disco anyway), and a pure shrink is not worth -// telling peers about. Suppressing this churn keeps peers' views stable. -// -// The decision is intentionally conservative: it gates the broadcast only, -// not storage. The node's full endpoint set (STUN included) is still stored -// and rides along the next substantive change or full MapResponse, so no -// reachable path is permanently hidden from peers. -// -// Limitation: headscale stores bare []netip.AddrPort with no per-endpoint -// type, so we can only classify the *new* request's endpoints (via the -// parallel newTypes slice). We therefore gate on whether any newly-added -// endpoint (present in new, absent from stored) is useful (non-STUN). When -// newTypes is absent or shorter than newEPs (older clients), the unknown -// endpoints are treated as useful, preserving the pre-existing always-broadcast -// behaviour and never hiding a genuinely new endpoint. -func endpointBroadcastWorthy( - stored, newEPs []netip.AddrPort, - newTypes []tailcfg.EndpointType, -) bool { - storedSet := make(map[netip.AddrPort]struct{}, len(stored)) - for _, ep := range stored { - storedSet[ep] = struct{}{} - } - - for i, ep := range newEPs { - if _, ok := storedSet[ep]; ok { - // Already known to peers; not a newly-added endpoint. - continue - } - - // A newly-added endpoint with no type information (older client) - // is treated as useful so we never hide a genuinely new endpoint. - t := tailcfg.EndpointUnknownType - if i < len(newTypes) { - t = newTypes[i] - } - - if isUsefulEndpointType(t) { - return true - } - } - - return false -} - -// isUsefulEndpointType reports whether an endpoint type is worth eagerly -// broadcasting to peers. STUN-derived endpoints are excluded because they are -// churny and unlikely to be directly reachable; magicsock's disco handles -// establishing those paths. -func isUsefulEndpointType(t tailcfg.EndpointType) bool { - return t != tailcfg.EndpointSTUN && t != tailcfg.EndpointSTUN4LocalPort -} - -// buildMapRequestChangeResponse determines the appropriate response type for a [tailcfg.MapRequest] update. +// buildMapRequestChangeResponse determines the appropriate response type for a MapRequest update. // Hostinfo changes require a full update, while endpoint/DERP changes can use lightweight patches. func buildMapRequestChangeResponse( id types.NodeID, @@ -3241,16 +2747,3 @@ func peerChangeEmpty(peerChange tailcfg.PeerChange) bool { peerChange.LastSeen == nil && peerChange.KeyExpiry == nil } - -// peerChangePersistWorthy reports whether a peer change carries anything that -// warrants a database write. It deliberately ignores LastSeen, which -// [Node.PeerChangeFromMapRequest] always stamps: a keepalive that only bumps -// LastSeen should not trigger a full-row UPDATE and policy rescan. -func peerChangePersistWorthy(peerChange tailcfg.PeerChange) bool { - return peerChange.Key != nil || - peerChange.DiscoKey != nil || - peerChange.Online != nil || - peerChange.Endpoints != nil || - peerChange.DERPRegion != 0 || - peerChange.KeyExpiry != nil -} diff --git a/hscontrol/state/tags.go b/hscontrol/state/tags.go index 9a7b25178..fbc05d9b2 100644 --- a/hscontrol/state/tags.go +++ b/hscontrol/state/tags.go @@ -20,13 +20,12 @@ var ( ErrRequestedTagsInvalidOrNotPermitted = errors.New("requested tags") ) -// ErrTaggedNodeHasUser is returned when a tagged node has a [types.Node.UserID] set. +// ErrTaggedNodeHasUser is returned when a tagged node has a UserID set. var ErrTaggedNodeHasUser = errors.New("tagged node must not have user_id set") // validateNodeOwnership ensures proper node ownership model. // A node must be either user-owned or tagged, and these are mutually exclusive: -// tagged nodes must not have a [types.Node.UserID], and user-owned nodes must -// not have tags. +// tagged nodes must not have a UserID, and user-owned nodes must not have tags. func validateNodeOwnership(node *types.Node) error { if node.IsTagged() { if len(node.Tags) == 0 { diff --git a/hscontrol/state/test_helpers.go b/hscontrol/state/test_helpers.go index c582b4db9..952031060 100644 --- a/hscontrol/state/test_helpers.go +++ b/hscontrol/state/test_helpers.go @@ -4,7 +4,7 @@ import ( "time" ) -// Test configuration for [NodeStore] batching. +// Test configuration for NodeStore batching. // These values are optimized for test speed rather than production use. const ( TestBatchSize = 5 diff --git a/hscontrol/tailsql.go b/hscontrol/tailsql.go index 25c6869a8..d6ef380ec 100644 --- a/hscontrol/tailsql.go +++ b/hscontrol/tailsql.go @@ -38,6 +38,9 @@ func runTailSQLService(ctx context.Context, logf logger.Logf, stateDir, dbPath s Hostname: opts.Hostname, Logf: logger.Discard, } + // if *doDebugLog { + // tsNode.Logf = logf + // } defer tsNode.Close() logf("Starting tailscale (hostname=%q)", opts.Hostname) @@ -81,9 +84,10 @@ func runTailSQLService(ctx context.Context, logf logger.Logf, stateDir, dbPath s go func() { _ = http.Serve(lst, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint:gosec target := base + r.RequestURI - http.Redirect(w, r, target, http.StatusPermanentRedirect) //nolint:gosec // G710: target prefixed by trusted base URL + http.Redirect(w, r, target, http.StatusPermanentRedirect) })) }() + // log.Printf("Redirecting HTTP to HTTPS at %q", base) // For the real service, start a separate listener. // Note: Replaces the port 80 listener. diff --git a/hscontrol/templates/apple.go b/hscontrol/templates/apple.go index 8376b19a5..3b1200696 100644 --- a/hscontrol/templates/apple.go +++ b/hscontrol/templates/apple.go @@ -1,196 +1,191 @@ package templates import ( + "fmt" + "github.com/chasefleming/elem-go" "github.com/chasefleming/elem-go/attrs" "github.com/chasefleming/elem-go/styles" ) func Apple(url string) *elem.Element { - return page( - "headscale - Apple", - H1(elem.Text("iOS configuration")), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Install the official Tailscale iOS client from the "), - externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), - ), - elem.Li( - nil, - elem.Text("Open the "), - elem.Strong(nil, elem.Text("Tailscale")), - elem.Text(" app"), - ), - elem.Li( - nil, - elem.Text("Click the account icon in the top-right corner and select "), - elem.Strong(nil, elem.Text("Log in…")), - ), - elem.Li( - nil, - elem.Text("Tap the top-right options menu button and select "), - elem.Strong(nil, elem.Text("Use custom coordination server")), - ), - elem.Li( - nil, - elem.Text("Enter your instance URL: "), - Code(elem.Text(url)), - ), - elem.Li( - nil, - elem.Text( - "Enter your credentials and log in. Headscale should now be working on your iOS device", + return HtmlStructure( + elem.Title(nil, + elem.Text("headscale - Apple")), + mdTypesetBody( + headscaleLogo(), + H1(elem.Text("iOS configuration")), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Install the official Tailscale iOS client from the "), + externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), + ), + elem.Li( + nil, + elem.Text("Open the "), + elem.Strong(nil, elem.Text("Tailscale")), + elem.Text(" app"), + ), + elem.Li( + nil, + elem.Text("Click the account icon in the top-right corner and select "), + elem.Strong(nil, elem.Text("Log in…")), + ), + elem.Li( + nil, + elem.Text("Tap the top-right options menu button and select "), + elem.Strong(nil, elem.Text("Use custom coordination server")), + ), + elem.Li( + nil, + elem.Text("Enter your instance URL: "), + Code(elem.Text(url)), + ), + elem.Li( + nil, + elem.Text( + "Enter your credentials and log in. Headscale should now be working on your iOS device", + ), ), ), - ), - H1(elem.Text("macOS configuration")), - H2(elem.Text("Command line")), - P( - elem.Text("Use Tailscale's login command to add your profile:"), - ), - codeBlockText("tailscale login --login-server "+url), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Option + Click the "), - elem.Strong(nil, elem.Text("Tailscale")), - elem.Text(" icon in the menu and hover over the "), - elem.Strong(nil, elem.Text("Debug")), - elem.Text(" menu"), + H1(elem.Text("macOS configuration")), + H2(elem.Text("Command line")), + P( + elem.Text("Use Tailscale's login command to add your profile:"), ), - elem.Li( - nil, - elem.Text("Under "), - elem.Strong(nil, elem.Text("Custom Login Server")), - elem.Text(", select "), - elem.Strong(nil, elem.Text("Add Account...")), - ), - elem.Li( - nil, - elem.Text("Enter "), - Code(elem.Text(url)), - elem.Text(" of the headscale instance and press "), - elem.Strong(nil, elem.Text("Add Account")), - ), - elem.Li( - nil, - elem.Text("Follow the login procedure in the browser"), - ), - ), - H2(elem.Text("Profiles")), - P( - elem.Text( - "Headscale can be set to the default server by installing a Headscale configuration profile:", - ), - ), - elem.Div( - attrs.Props{attrs.Style: styles.Props{styles.MarginTop: spaceL, styles.MarginBottom: spaceL}.ToInline()}, - downloadButton("/apple/macos-app-store", "macOS AppStore profile"), - downloadButton("/apple/macos-standalone", "macOS Standalone profile"), - ), - Ol( - elem.Li( - nil, - elem.Text( - "Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed", + Pre(PreCode("tailscale login --login-server "+url)), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Option + Click the "), + elem.Strong(nil, elem.Text("Tailscale")), + elem.Text(" icon in the menu and hover over the "), + elem.Strong(nil, elem.Text("Debug")), + elem.Text(" menu"), + ), + elem.Li(nil, + elem.Text("Under "), + elem.Strong(nil, elem.Text("Custom Login Server")), + elem.Text(", select "), + elem.Strong(nil, elem.Text("Add Account...")), + ), + elem.Li( + nil, + elem.Text("Enter "), + Code(elem.Text(url)), + elem.Text(" of the headscale instance and press "), + elem.Strong(nil, elem.Text("Add Account")), + ), + elem.Li(nil, + elem.Text("Follow the login procedure in the browser"), ), ), - elem.Li( - nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("System Preferences")), - elem.Text(" and go to "), - elem.Strong(nil, elem.Text("Profiles")), + H2(elem.Text("Profiles")), + P( + elem.Text( + "Headscale can be set to the default server by installing a Headscale configuration profile:", + ), ), - elem.Li( - nil, - elem.Text("Find and install the "), - elem.Strong(nil, elem.Text("Headscale")), - elem.Text(" profile"), + elem.Div(attrs.Props{attrs.Style: styles.Props{styles.MarginTop: spaceL, styles.MarginBottom: spaceL}.ToInline()}, + downloadButton("/apple/macos-app-store", "macOS AppStore profile"), + downloadButton("/apple/macos-standalone", "macOS Standalone profile"), ), - elem.Li( - nil, + Ol( + elem.Li( + nil, + elem.Text( + "Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed", + ), + ), + elem.Li(nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("System Preferences")), + elem.Text(" and go to "), + elem.Strong(nil, elem.Text("Profiles")), + ), + elem.Li(nil, + elem.Text("Find and install the "), + elem.Strong(nil, elem.Text("Headscale")), + elem.Text(" profile"), + ), + elem.Li(nil, + elem.Text("Restart "), + elem.Strong(nil, elem.Text("Tailscale.app")), + elem.Text(" and log in"), + ), + ), + orDivider(), + P( + elem.Text( + "Use your terminal to configure the default setting for Tailscale by issuing one of the following commands:", + ), + ), + P(elem.Text("For app store client:")), + Pre(PreCode("defaults write io.tailscale.ipn.macos ControlURL "+url)), + P(elem.Text("For standalone client:")), + Pre(PreCode("defaults write io.tailscale.ipn.macsys ControlURL "+url)), + P( elem.Text("Restart "), elem.Strong(nil, elem.Text("Tailscale.app")), - elem.Text(" and log in"), + elem.Text(" and log in."), ), - ), - orDivider(), - P( - elem.Text( - "Use your terminal to configure the default setting for Tailscale by issuing one of the following commands:", - ), - ), - P(elem.Text("For app store client:")), - codeBlockText("defaults write io.tailscale.ipn.macos ControlURL "+url), - P(elem.Text("For standalone client:")), - codeBlockText("defaults write io.tailscale.ipn.macsys ControlURL "+url), - P( - elem.Text("Restart "), - elem.Strong(nil, elem.Text("Tailscale.app")), - elem.Text(" and log in."), - ), - warningBox("Caution", "You should always download and inspect the profile before installing it."), - P(elem.Text("For app store client:")), - codeBlockText("curl "+url+"/apple/macos-app-store"), - P(elem.Text("For standalone client:")), - codeBlockText("curl "+url+"/apple/macos-standalone"), - H1(elem.Text("tvOS configuration")), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Install the official Tailscale tvOS client from the "), - externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), - ), - elem.Li( - nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("Settings")), - elem.Text(" (the Apple tvOS settings) > "), - elem.Strong(nil, elem.Text("Apps")), - elem.Text(" > "), - elem.Strong(nil, elem.Text("Tailscale")), - ), - elem.Li( - nil, - elem.Text("Enter "), - Code(elem.Text(url)), - elem.Text(" under "), - elem.Strong(nil, elem.Text("ALTERNATE COORDINATION SERVER URL")), - ), - elem.Li( - nil, - elem.Text("Return to the tvOS "), - elem.Strong(nil, elem.Text("Home")), - elem.Text(" screen"), - ), - elem.Li( - nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("Tailscale")), - ), - elem.Li( - nil, - elem.Text("Select "), - elem.Strong(nil, elem.Text("Install VPN configuration")), - ), - elem.Li( - nil, - elem.Text("Select "), - elem.Strong(nil, elem.Text("Allow")), - ), - elem.Li( - nil, - elem.Text("Scan the QR code and follow the login procedure"), - ), - elem.Li( - nil, - elem.Text("Headscale should now be working on your tvOS device"), + warningBox("Caution", "You should always download and inspect the profile before installing it."), + P(elem.Text("For app store client:")), + Pre(PreCode(fmt.Sprintf(`curl %s/apple/macos-app-store`, url))), + P(elem.Text("For standalone client:")), + Pre(PreCode(fmt.Sprintf(`curl %s/apple/macos-standalone`, url))), + H1(elem.Text("tvOS configuration")), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Install the official Tailscale tvOS client from the "), + externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), + ), + elem.Li( + nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("Settings")), + elem.Text(" (the Apple tvOS settings) > "), + elem.Strong(nil, elem.Text("Apps")), + elem.Text(" > "), + elem.Strong(nil, elem.Text("Tailscale")), + ), + elem.Li( + nil, + elem.Text("Enter "), + Code(elem.Text(url)), + elem.Text(" under "), + elem.Strong(nil, elem.Text("ALTERNATE COORDINATION SERVER URL")), + ), + elem.Li(nil, + elem.Text("Return to the tvOS "), + elem.Strong(nil, elem.Text("Home")), + elem.Text(" screen"), + ), + elem.Li(nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("Tailscale")), + ), + elem.Li(nil, + elem.Text("Select "), + elem.Strong(nil, elem.Text("Install VPN configuration")), + ), + elem.Li(nil, + elem.Text("Select "), + elem.Strong(nil, elem.Text("Allow")), + ), + elem.Li(nil, + elem.Text("Scan the QR code and follow the login procedure"), + ), + elem.Li(nil, + elem.Text("Headscale should now be working on your tvOS device"), + ), ), + pageFooter(), ), ) } diff --git a/hscontrol/templates/auth_error.go b/hscontrol/templates/auth_error.go index 8bcaa8063..7b48974c1 100644 --- a/hscontrol/templates/auth_error.go +++ b/hscontrol/templates/auth_error.go @@ -30,8 +30,12 @@ func AuthError(result AuthErrorResult) *elem.Element { elem.Text(result.Message), ) - return page( - result.Title, - box, + return HtmlStructure( + elem.Title(nil, elem.Text(result.Title)), + mdTypesetBody( + headscaleLogo(), + box, + pageFooter(), + ), ) } diff --git a/hscontrol/templates/auth_success.go b/hscontrol/templates/auth_success.go index 631de3b14..c0f275e54 100644 --- a/hscontrol/templates/auth_success.go +++ b/hscontrol/templates/auth_success.go @@ -41,20 +41,22 @@ func AuthSuccess(result AuthSuccessResult) *elem.Element { elem.Text(". "+result.Message), ) - return page( - result.Title, - box, - H2(elem.Text("Getting started")), - P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")), - Ul( - elem.Li( - nil, - externalLink("https://headscale.net/stable/", "Headscale documentation"), - ), - elem.Li( - nil, - externalLink("https://tailscale.com/docs", "Tailscale docs"), + return HtmlStructure( + elem.Title(nil, elem.Text(result.Title)), + mdTypesetBody( + headscaleLogo(), + box, + H2(elem.Text("Getting started")), + P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")), + Ul( + elem.Li(nil, + externalLink("https://headscale.net/stable/", "Headscale documentation"), + ), + elem.Li(nil, + externalLink("https://tailscale.com/docs", "Tailscale docs"), + ), ), + pageFooter(), ), ) } diff --git a/hscontrol/templates/auth_web.go b/hscontrol/templates/auth_web.go index ff97250da..8b6d6f971 100644 --- a/hscontrol/templates/auth_web.go +++ b/hscontrol/templates/auth_web.go @@ -8,10 +8,14 @@ import ( // to complete an authentication or registration flow. // It is used by both the registration and auth-approve web handlers. func AuthWeb(title, description, command string) *elem.Element { - return page( - title+" - Headscale", - H1(elem.Text(title)), - P(elem.Text(description)), - codeBlockText(command), + return HtmlStructure( + elem.Title(nil, elem.Text(title+" - Headscale")), + mdTypesetBody( + headscaleLogo(), + H1(elem.Text(title)), + P(elem.Text(description)), + Pre(PreCode(command)), + pageFooter(), + ), ) } diff --git a/hscontrol/templates/design.go b/hscontrol/templates/design.go index 422d85db7..55b965081 100644 --- a/hscontrol/templates/design.go +++ b/hscontrol/templates/design.go @@ -10,6 +10,41 @@ import ( // These constants define the visual language for all Headscale HTML templates. // They ensure consistency across all pages and make it easy to maintain and update the design. +// Color System +// EXTRACTED FROM: https://headscale.net/stable/assets/stylesheets/main.342714a4.min.css +// Material for MkDocs design system - exact values from official docs. +const ( + // Text colors - from --md-default-fg-color CSS variables. + colorTextPrimary = "#000000de" //nolint:unused // rgba(0,0,0,0.87) - Body text + colorTextSecondary = "#0000008a" //nolint:unused // rgba(0,0,0,0.54) - Headings (--md-default-fg-color--light) + colorTextTertiary = "#00000052" //nolint:unused // rgba(0,0,0,0.32) - Lighter text + colorTextLightest = "#00000012" //nolint:unused // rgba(0,0,0,0.07) - Lightest text + + // Code colors - from --md-code-* CSS variables. + colorCodeFg = "#36464e" //nolint:unused // Code text color (--md-code-fg-color) + colorCodeBg = "#f5f5f5" //nolint:unused // Code background (--md-code-bg-color) + + // Border colors. + colorBorderLight = "#e5e7eb" //nolint:unused // Light borders + colorBorderMedium = "#d1d5db" //nolint:unused // Medium borders + + // Background colors. + colorBackgroundPage = "#ffffff" //nolint:unused // Page background + colorBackgroundCard = "#ffffff" //nolint:unused // Card/content background + + // Accent colors - from --md-primary/accent-fg-color. + colorPrimaryAccent = "#4051b5" //nolint:unused // Primary accent (links) + colorAccent = "#526cfe" //nolint:unused // Secondary accent + + // Success colors. + colorSuccess = "#059669" //nolint:unused // Success states + colorSuccessLight = "#d1fae5" //nolint:unused // Success backgrounds + + // Error colors. + colorError = "#dc2626" //nolint:unused // Error states (red-600) + colorErrorLight = "#fee2e2" //nolint:unused // Error backgrounds (red-100) +) + // Spacing System // Based on 4px/8px base unit for consistent rhythm. // Uses rem units for scalability with user font size preferences. @@ -23,25 +58,272 @@ const ( space3XL = "4rem" //nolint:unused // 64px - 3x extra large spacing ) -// Shared CSS value constants used across templates. -const ( - cssBorderHS = "1px solid var(--hs-border)" //nolint:unused // Shared HS border - cssCenter = "center" //nolint:unused // Center alignment -) - // Typography System // EXTRACTED FROM: https://headscale.net/stable/assets/stylesheets/main.342714a4.min.css // Material for MkDocs typography - exact values from .md-typeset CSS. const ( + // Font families - from CSS custom properties. + fontFamilySystem = `"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif` //nolint:unused + fontFamilyCode = `"Roboto Mono", "SF Mono", Monaco, "Cascadia Code", Consolas, "Courier New", monospace` //nolint:unused + // Font sizes - from .md-typeset CSS rules. - fontSizeBase = "0.8rem" //nolint:unused // 12.8px - Base text (.md-typeset) - fontSizeH3 = "1.25em" //nolint:unused // 1.25x base - Subsection headings - fontSizeSmall = "0.8em" //nolint:unused // 0.8x base - Small text + fontSizeBase = "0.8rem" //nolint:unused // 12.8px - Base text (.md-typeset) + fontSizeH1 = "2em" //nolint:unused // 2x base - Main headings + fontSizeH2 = "1.5625em" //nolint:unused // 1.5625x base - Section headings + fontSizeH3 = "1.25em" //nolint:unused // 1.25x base - Subsection headings + fontSizeSmall = "0.8em" //nolint:unused // 0.8x base - Small text + fontSizeCode = "0.85em" //nolint:unused // 0.85x base - Inline code // Line heights - from .md-typeset CSS rules. lineHeightBase = "1.6" //nolint:unused // Body text (.md-typeset) + lineHeightH1 = "1.3" //nolint:unused // H1 headings + lineHeightH2 = "1.4" //nolint:unused // H2 headings + lineHeightH3 = "1.5" //nolint:unused // H3 headings + lineHeightCode = "1.4" //nolint:unused // Code blocks (pre) ) +// Responsive Container Component +// Creates a centered container with responsive padding and max-width. +// Mobile-first approach: starts at 100% width with padding, constrains on larger screens. +// +//nolint:unused // Reserved for future use in Phase 4. +func responsiveContainer(children ...elem.Node) *elem.Element { + return elem.Div(attrs.Props{ + attrs.Style: styles.Props{ + styles.Width: "100%", + styles.MaxWidth: "min(800px, 90vw)", // Responsive: 90% of viewport or 800px max + styles.Margin: "0 auto", // Center horizontally + styles.Padding: "clamp(1rem, 5vw, 2.5rem)", // Fluid padding: 16px to 40px + }.ToInline(), + }, children...) +} + +// Card Component +// Reusable card for grouping related content with visual separation. +// Parameters: +// - title: Optional title for the card (empty string for no title) +// - children: Content elements to display in the card +// +//nolint:unused // Reserved for future use in Phase 4. +func card(title string, children ...elem.Node) *elem.Element { + cardContent := children + if title != "" { + cardContent = append([]elem.Node{ + elem.H3(attrs.Props{ + attrs.Style: styles.Props{ + styles.MarginTop: "0", + styles.MarginBottom: spaceM, + }.ToInline(), + }, elem.Text(title)), + }, children...) + } + + return elem.Div(attrs.Props{ + attrs.Style: styles.Props{ + styles.Background: "var(--hs-bg)", + styles.Border: "1px solid var(--hs-border)", + styles.BorderRadius: "0.5rem", + styles.Padding: "clamp(1rem, 3vw, 1.5rem)", + styles.MarginBottom: spaceL, + styles.BoxShadow: "0 1px 3px rgba(0,0,0,0.1)", + }.ToInline(), + }, cardContent...) +} + +// Code Block Component +// EXTRACTED FROM: .md-typeset pre CSS rules +// Exact styling from Material for MkDocs documentation. +// +//nolint:unused // Used across apple.go, windows.go, register_web.go templates. +func codeBlock(code string) *elem.Element { + return elem.Pre(attrs.Props{ + attrs.Style: styles.Props{ + styles.Display: "block", + styles.Padding: "0.77em 1.18em", // From .md-typeset pre + styles.Border: "none", // No border in original + styles.BorderRadius: "0.1rem", // From .md-typeset code + styles.BackgroundColor: colorCodeBg, // #f5f5f5 + styles.FontFamily: fontFamilyCode, // Roboto Mono + styles.FontSize: fontSizeCode, // 0.85em + styles.LineHeight: lineHeightCode, // 1.4 + styles.OverflowX: "auto", // Horizontal scroll + "overflow-wrap": "break-word", // Word wrapping + "word-wrap": "break-word", // Legacy support + styles.WhiteSpace: "pre-wrap", // Preserve whitespace + styles.MarginTop: spaceM, // 1em + styles.MarginBottom: spaceM, // 1em + styles.Color: colorCodeFg, // #36464e + styles.BoxShadow: "none", // No shadow in original + }.ToInline(), + }, + elem.Code(nil, elem.Text(code)), + ) +} + +// Base Typeset Styles +// Returns inline styles for the main content container that matches .md-typeset. +// EXTRACTED FROM: .md-typeset CSS rule from Material for MkDocs. +// +//nolint:unused // Used in general.go for mdTypesetBody. +func baseTypesetStyles() styles.Props { + return styles.Props{ + styles.FontSize: fontSizeBase, // 0.8rem + styles.LineHeight: lineHeightBase, // 1.6 + styles.Color: colorTextPrimary, + styles.FontFamily: fontFamilySystem, + "overflow-wrap": "break-word", + styles.TextAlign: "left", + } +} + +// H1 Styles +// Returns inline styles for H1 headings that match .md-typeset h1. +// EXTRACTED FROM: .md-typeset h1 CSS rule from Material for MkDocs. +// +//nolint:unused // Used across templates for main headings. +func h1Styles() styles.Props { + return styles.Props{ + styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54) + styles.FontSize: fontSizeH1, // 2em + styles.LineHeight: lineHeightH1, // 1.3 + styles.Margin: "0 0 1.25em", + styles.FontWeight: "300", + "letter-spacing": "-0.01em", + styles.FontFamily: fontFamilySystem, // Roboto + "overflow-wrap": "break-word", + } +} + +// H2 Styles +// Returns inline styles for H2 headings that match .md-typeset h2. +// EXTRACTED FROM: .md-typeset h2 CSS rule from Material for MkDocs. +// +//nolint:unused // Used across templates for section headings. +func h2Styles() styles.Props { + return styles.Props{ + styles.FontSize: fontSizeH2, // 1.5625em + styles.LineHeight: lineHeightH2, // 1.4 + styles.Margin: "1.6em 0 0.64em", + styles.FontWeight: "300", + "letter-spacing": "-0.01em", + styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54) + styles.FontFamily: fontFamilySystem, // Roboto + "overflow-wrap": "break-word", + } +} + +// H3 Styles +// Returns inline styles for H3 headings that match .md-typeset h3. +// EXTRACTED FROM: .md-typeset h3 CSS rule from Material for MkDocs. +// +//nolint:unused // Used across templates for subsection headings. +func h3Styles() styles.Props { + return styles.Props{ + styles.FontSize: fontSizeH3, // 1.25em + styles.LineHeight: lineHeightH3, // 1.5 + styles.Margin: "1.6em 0 0.8em", + styles.FontWeight: "400", + "letter-spacing": "-0.01em", + styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54) + styles.FontFamily: fontFamilySystem, // Roboto + "overflow-wrap": "break-word", + } +} + +// Paragraph Styles +// Returns inline styles for paragraphs that match .md-typeset p. +// EXTRACTED FROM: .md-typeset p CSS rule from Material for MkDocs. +// +//nolint:unused // Used for consistent paragraph spacing. +func paragraphStyles() styles.Props { + return styles.Props{ + styles.Margin: "1em 0", + styles.FontFamily: fontFamilySystem, // Roboto + styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset + styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset + styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87) + "overflow-wrap": "break-word", + } +} + +// Ordered List Styles +// Returns inline styles for ordered lists that match .md-typeset ol. +// EXTRACTED FROM: .md-typeset ol CSS rule from Material for MkDocs. +// +//nolint:unused // Used for numbered instruction lists. +func orderedListStyles() styles.Props { + return styles.Props{ + styles.MarginBottom: "1em", + styles.MarginTop: "1em", + styles.PaddingLeft: "2em", + styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset + styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset + styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset + styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87) - inherited from .md-typeset + "overflow-wrap": "break-word", + } +} + +// Unordered List Styles +// Returns inline styles for unordered lists that match .md-typeset ul. +// EXTRACTED FROM: .md-typeset ul CSS rule from Material for MkDocs. +// +//nolint:unused // Used for bullet point lists. +func unorderedListStyles() styles.Props { + return styles.Props{ + styles.MarginBottom: "1em", + styles.MarginTop: "1em", + styles.PaddingLeft: "2em", + styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset + styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset + styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset + styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87) - inherited from .md-typeset + "overflow-wrap": "break-word", + } +} + +// Link Styles +// Returns inline styles for links that match .md-typeset a. +// EXTRACTED FROM: .md-typeset a CSS rule from Material for MkDocs. +// Note: Hover states cannot be implemented with inline styles. +// +//nolint:unused // Used for text links. +func linkStyles() styles.Props { + return styles.Props{ + styles.Color: colorPrimaryAccent, // #4051b5 - var(--md-primary-fg-color) + styles.TextDecoration: "none", + "word-break": "break-word", + styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset + } +} + +// Inline Code Styles (updated) +// Returns inline styles for inline code that matches .md-typeset code. +// EXTRACTED FROM: .md-typeset code CSS rule from Material for MkDocs. +// +//nolint:unused // Used for inline code snippets. +func inlineCodeStyles() styles.Props { + return styles.Props{ + styles.BackgroundColor: colorCodeBg, // #f5f5f5 + styles.Color: colorCodeFg, // #36464e + styles.BorderRadius: "0.1rem", + styles.FontSize: fontSizeCode, // 0.85em + styles.FontFamily: fontFamilyCode, // Roboto Mono + styles.Padding: "0 0.2941176471em", + "word-break": "break-word", + } +} + +// Inline Code Component +// For inline code snippets within text. +// +//nolint:unused // Reserved for future inline code usage. +func inlineCode(code string) *elem.Element { + return elem.Code(attrs.Props{ + attrs.Style: inlineCodeStyles().ToInline(), + }, elem.Text(code)) +} + // orDivider creates a visual "or" divider between sections. // Styled with lines on either side for better visual separation. // @@ -53,17 +335,16 @@ func orDivider() *elem.Element { styles.BackgroundColor: "var(--hs-border)", }.ToInline() - return elem.Div( - attrs.Props{ - attrs.Style: styles.Props{ - styles.Display: "flex", - styles.AlignItems: cssCenter, - styles.Gap: spaceM, - styles.MarginTop: space2XL, - styles.MarginBottom: space2XL, - styles.Width: "100%", - }.ToInline(), - }, + return elem.Div(attrs.Props{ + attrs.Style: styles.Props{ + styles.Display: "flex", + styles.AlignItems: "center", + styles.Gap: spaceM, + styles.MarginTop: space2XL, + styles.MarginBottom: space2XL, + styles.Width: "100%", + }.ToInline(), + }, elem.Div(attrs.Props{attrs.Style: lineStyle}), elem.Strong(attrs.Props{ attrs.Style: styles.Props{ @@ -78,40 +359,34 @@ func orDivider() *elem.Element { ) } -// feedbackBox creates a coloured feedback box with an icon and a bold heading. -// colorVar provides both the border and heading colour, bgVar the background; -// role and ariaLive set the accessibility attributes. Children render below the -// heading. +// successBox creates a green success feedback box with a checkmark icon. +// The heading is displayed as bold green text, and children are rendered below it. +// Pairs with warningBox for consistent feedback styling. // -//nolint:unused // Wrapped by successBox and errorBox. -func feedbackBox( - icon elem.Node, - colorVar, bgVar, role, ariaLive, heading string, - children ...elem.Node, -) *elem.Element { +//nolint:unused // Used in auth_success.go template. +func successBox(heading string, children ...elem.Node) *elem.Element { return elem.Div( attrs.Props{ attrs.Style: styles.Props{ styles.Display: "flex", - styles.AlignItems: cssCenter, + styles.AlignItems: "center", styles.Gap: spaceM, styles.Padding: spaceL, - styles.BackgroundColor: bgVar, - styles.Border: "1px solid " + colorVar, - styles.BorderRadius: spaceS, + styles.BackgroundColor: "var(--hs-success-bg)", + styles.Border: "1px solid var(--hs-success)", + styles.BorderRadius: "0.5rem", styles.MarginBottom: spaceXL, }.ToInline(), - attrs.Role: role, - "aria-live": ariaLive, + attrs.Role: "status", + "aria-live": "polite", }, - icon, - elem.Div( - nil, + checkboxIcon(), + elem.Div(nil, append([]elem.Node{ elem.Strong(attrs.Props{ attrs.Style: styles.Props{ styles.Display: "block", - styles.Color: colorVar, + styles.Color: "var(--hs-success)", styles.FontSize: fontSizeH3, styles.FontWeight: "700", styles.MarginBottom: spaceXS, @@ -122,19 +397,6 @@ func feedbackBox( ) } -// successBox creates a green success feedback box with a checkmark icon. -// The heading is displayed as bold green text, and children are rendered below it. -// Pairs with warningBox for consistent feedback styling. -// -//nolint:unused // Used in auth_success.go template. -func successBox(heading string, children ...elem.Node) *elem.Element { - return feedbackBox( - checkboxIcon(), - "var(--hs-success)", "var(--hs-success-bg)", "status", "polite", - heading, children..., - ) -} - // checkboxIcon returns the success checkbox SVG icon as raw HTML. func checkboxIcon() elem.Node { return elem.Raw(` + + + + + + +
+ + +`)) + + var payload bytes.Buffer + if err := swaggerTemplate.Execute(&payload, struct{}{}); err != nil { //nolint:noinlineerr + log.Error(). + Caller(). + Err(err). + Msg("Could not render Swagger") + + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(http.StatusInternalServerError) + + _, err := writer.Write([]byte("Could not render Swagger")) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } + + return + } + + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.WriteHeader(http.StatusOK) + + _, err := writer.Write(payload.Bytes()) + if err != nil { + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } +} + +func SwaggerAPIv1( + writer http.ResponseWriter, + req *http.Request, +) { + writer.Header().Set("Content-Type", "application/json; charset=utf-8") + writer.WriteHeader(http.StatusOK) + + if _, err := writer.Write(apiV1JSON); err != nil { //nolint:noinlineerr + log.Error(). + Caller(). + Err(err). + Msg("Failed to write response") + } +} diff --git a/tools/capver/main.go b/tools/capver/main.go index 368214c83..21c92d260 100644 --- a/tools/capver/main.go +++ b/tools/capver/main.go @@ -10,14 +10,15 @@ import ( "go/format" "io" "log" - "maps" "net/http" "os" "regexp" "slices" + "sort" "strconv" "strings" + xmaps "golang.org/x/exp/maps" "tailscale.com/tailcfg" ) @@ -196,7 +197,7 @@ func getCapabilityVersions(ctx context.Context) (map[string]tailcfg.CapabilityVe minorVersions := getMinorVersionsFromTags(tags) log.Printf("Found %d minor versions", len(minorVersions)) - // Regular expression to find the [tailcfg.CurrentCapabilityVersion] line + // Regular expression to find the CurrentCapabilityVersion line re := regexp.MustCompile(`const CurrentCapabilityVersion CapabilityVersion = (\d+)`) versions := make(map[string]tailcfg.CapabilityVersion) @@ -230,7 +231,7 @@ func getCapabilityVersions(ctx context.Context) (map[string]tailcfg.CapabilityVe continue } - // Find the [tailcfg.CurrentCapabilityVersion] + // Find the CurrentCapabilityVersion matches := re.FindStringSubmatch(string(body)) if len(matches) > 1 { capabilityVersionStr := matches[1] @@ -245,7 +246,8 @@ func getCapabilityVersions(ctx context.Context) (map[string]tailcfg.CapabilityVe func calculateMinSupportedCapabilityVersion(versions map[string]tailcfg.CapabilityVersion) tailcfg.CapabilityVersion { // Since we now store minor versions directly, just sort and take the oldest of the latest N - minorVersions := slices.Sorted(maps.Keys(versions)) + minorVersions := xmaps.Keys(versions) + sort.Strings(minorVersions) supportedCount := min(len(minorVersions), supportedMajorMinorVersions) @@ -259,23 +261,6 @@ func calculateMinSupportedCapabilityVersion(versions map[string]tailcfg.Capabili return versions[oldestSupportedMinor] } -// firstTailscaleVerPerCapVer inverts versions into a map from each capability -// version to the first (lowest-sorted) Tailscale minor version reporting it. -func firstTailscaleVerPerCapVer(versions map[string]tailcfg.CapabilityVersion) map[tailcfg.CapabilityVersion]string { - sortedVersions := slices.Sorted(maps.Keys(versions)) - - capVerToTailscaleVer := make(map[tailcfg.CapabilityVersion]string) - - for _, v := range sortedVersions { - capabilityVersion := versions[v] - if _, ok := capVerToTailscaleVer[capabilityVersion]; !ok { - capVerToTailscaleVer[capabilityVersion] = v - } - } - - return capVerToTailscaleVer -} - func writeCapabilityVersionsToFile(versions map[string]tailcfg.CapabilityVersion, minSupportedCapVer tailcfg.CapabilityVersion) error { // Generate the Go code as a string var content strings.Builder @@ -285,7 +270,8 @@ func writeCapabilityVersionsToFile(versions map[string]tailcfg.CapabilityVersion content.WriteString("\n\n") content.WriteString("var tailscaleToCapVer = map[string]tailcfg.CapabilityVersion{\n") - sortedVersions := slices.Sorted(maps.Keys(versions)) + sortedVersions := xmaps.Keys(versions) + sort.Strings(sortedVersions) for _, version := range sortedVersions { fmt.Fprintf(&content, "\t\"%s\": %d,\n", version, versions[version]) @@ -296,21 +282,35 @@ func writeCapabilityVersionsToFile(versions map[string]tailcfg.CapabilityVersion content.WriteString("\n\n") content.WriteString("var capVerToTailscaleVer = map[tailcfg.CapabilityVersion]string{\n") - capVerToTailscaleVer := firstTailscaleVerPerCapVer(versions) + capVarToTailscaleVer := make(map[tailcfg.CapabilityVersion]string) - capsSorted := slices.Sorted(maps.Keys(capVerToTailscaleVer)) + for _, v := range sortedVersions { + capabilityVersion := versions[v] + + // If it is already set, skip and continue, + // we only want the first tailscale version per + // capability version. + if _, ok := capVarToTailscaleVer[capabilityVersion]; ok { + continue + } + + capVarToTailscaleVer[capabilityVersion] = v + } + + capsSorted := xmaps.Keys(capVarToTailscaleVer) + slices.Sort(capsSorted) for _, capVer := range capsSorted { - fmt.Fprintf(&content, "\t%d:\t\t\"%s\",\n", capVer, capVerToTailscaleVer[capVer]) + fmt.Fprintf(&content, "\t%d:\t\t\"%s\",\n", capVer, capVarToTailscaleVer[capVer]) } content.WriteString("}\n\n") - // Add the [capver.SupportedMajorMinorVersions] constant + // Add the SupportedMajorMinorVersions constant content.WriteString("// SupportedMajorMinorVersions is the number of major.minor Tailscale versions supported.\n") fmt.Fprintf(&content, "const SupportedMajorMinorVersions = %d\n\n", supportedMajorMinorVersions) - // Add the [capver.MinSupportedCapabilityVersion] constant + // Add the MinSupportedCapabilityVersion constant content.WriteString("// MinSupportedCapabilityVersion represents the minimum capability version\n") content.WriteString("// supported by this Headscale instance (latest 10 minor versions)\n") fmt.Fprintf(&content, "const MinSupportedCapabilityVersion tailcfg.CapabilityVersion = %d\n", minSupportedCapVer) @@ -332,7 +332,8 @@ func writeCapabilityVersionsToFile(versions map[string]tailcfg.CapabilityVersion func writeTestDataFile(versions map[string]tailcfg.CapabilityVersion, minSupportedCapVer tailcfg.CapabilityVersion) error { // Sort minor versions - minorVersions := slices.Sorted(maps.Keys(versions)) + minorVersions := xmaps.Keys(versions) + sort.Strings(minorVersions) // Take latest N supportedCount := min(len(minorVersions), supportedMajorMinorVersions) @@ -347,7 +348,7 @@ func writeTestDataFile(versions map[string]tailcfg.CapabilityVersion, minSupport content.WriteString("// Generated DO NOT EDIT\n\n") content.WriteString("import \"tailscale.com/tailcfg\"\n\n") - // Generate complete test struct for [capver.TailscaleLatestMajorMinor] + // Generate complete test struct for TailscaleLatestMajorMinor content.WriteString("var tailscaleLatestMajorMinorTests = []struct {\n") content.WriteString("\tn int\n") content.WriteString("\tstripV bool\n") @@ -397,9 +398,18 @@ func writeTestDataFile(versions map[string]tailcfg.CapabilityVersion, minSupport content.WriteString("}\n\n") // Build capVerToTailscaleVer for test data - capVerToTailscaleVer := firstTailscaleVerPerCapVer(versions) + capVerToTailscaleVer := make(map[tailcfg.CapabilityVersion]string) + sortedVersions := xmaps.Keys(versions) + sort.Strings(sortedVersions) - // Generate complete test struct for [capver.CapVerMinimumTailscaleVersion] + for _, v := range sortedVersions { + capabilityVersion := versions[v] + if _, ok := capVerToTailscaleVer[capabilityVersion]; !ok { + capVerToTailscaleVer[capabilityVersion] = v + } + } + + // Generate complete test struct for CapVerMinimumTailscaleVersion content.WriteString("var capVerMinimumTailscaleVersionTests = []struct {\n") content.WriteString("\tinput tailcfg.CapabilityVersion\n") content.WriteString("\texpected string\n") @@ -410,7 +420,8 @@ func writeTestDataFile(versions map[string]tailcfg.CapabilityVersion, minSupport fmt.Fprintf(&content, "\t{%d, \"%s\"},\n", minSupportedCapVer, minVersionString) // Add a few more test cases - capsSorted := slices.Sorted(maps.Keys(capVerToTailscaleVer)) + capsSorted := xmaps.Keys(capVerToTailscaleVer) + slices.Sort(capsSorted) testCount := 0 for _, capVer := range capsSorted {